Agenda

Estas notas estan basadas en el libro Graphical Data Analysis with R, la bibliografía se encuentra en el temario

Exploratory Data Analysis

Es el PRIMER paso en el análisis de datos, es un punto CRÍTICO para realizar un análisis correcto -con contexto, sin sesgo, desde diferentes puntos de vista-

Puedes pensarlo como la primera aproximación al problema que quieres resolver

Objetivos

  • Detectar de errores
  • Detectar de datos anómalos, faltantes
  • Detectar de datos aislados
  • Verficar de que tienes datos relevantes y suficientes para contestar tu(s) pregunta(s)
  • Verificar si la(s) pregunta(s) es la correcta
  • ¿se requiren más datos?
  • ¿Qué otras variables puedes obtener de este set de datos que te permitan contestar tu(s) pregunta(s)? \(\rightarrow\) lo veremos en feature extraction / feature selection
  • Verificar de suposiciones -tuyas-
  • Selección preliminar de los modelos apropiados
  • Determinar relaciones entre las variables explicativas
  • Evaluar la dirección y tamaño -aproximado- de las relaciones entre las variables explicativas y la(s) variable(s) de salida -variable target-
  • Empezar a visualizar cómo sería la respuesta a la(s) pregunta(s) que quieres contestar con este set de datos
  • También nos ayudará cuando querramos automatizar la evaluación y tuneo de modelos

Como verás ayuda a hacer un modelado más robusto

De manera no estricta, si un análisis de datos no incluye modelado estadístico formal y/o inferencia/predicción entonces el análisis es EDA

Tipos de EDA

Normalmente los datos que tenemos para analizar vienen en un formato rectangular (..aunque nunca limpios ni cómo los necesitamos (╯°□°)╯︵ ┻━┻, pero lo veremos más adelante) donde un renglón es una observación de un experimento y cada columna es: el identificador del sujeto, la variable de salida y las variables explicativas.

Analizar los datos de esta tabla resulta tedioso, aburrido y abrumador de entender, enter EDA :), las técnicas utilizadas en el EDA permiten esconder ciertas cosas de los datos para hacer sobresalir o dejar claras otras.

Hay 2 grandes maneras cruzadas de clasificar el tipo de EDA:

  1. Hacerlo de manera gŕafica o no -GEDA Graphical Exploratory Data Analysis, EDA Exploratory Data Analysis-
  2. Cada método es univariado o multivariado -nonrmalmente bivariada-
  • Análisis exploratorio no gráfico: Generalmente incluye calcular el resúmen estadístico de
  • Análisis exploratorio gráfico: Resúmen los datos de forma gráfica
  • Univariada: Analizan una variable a la vez
  • Multivariada: Analizan 2 o más variables a la vez para explorar relaciones entre variables -normalmente bivariada-

Se recomienda primero hacer un EDA univariado a cada variable que forme parte de un EDA multivariado ANTES de hacer el EDA multivariado

Después de clasificar en estos 4 tipos cruzados existen más divisiones al EDA basadas en:

  1. El rol de la variable: salida o explicativa
  2. El tipo de la variable: categórica o numérica

Algunos consejos

  • Sí existen guías para qué técnica de EDA utilizar dependiendo de las circunstancias, pero normalmente eso se va mejorando con la experiencia
  • No existe un tipo óptimo de gráfica, prueba varias \(\rightarrow\) aunque si hay reglas que debes seguir para la visualización
  • Antes de ponerte a hacer gráficas piensa qué quieres visualizar y por qué

Ejemplos de lo que NO debes hacer en visualizaciones

  • NO Graficar pies! ¿por qué?

Aunque es una visualización muy solicitada por gente de negocio, existe una mejor manera de representar la misma información. El objetivo de una visualización de pie es mostrar cómo un 100% se distribuye entre diferentes valores de una variable. Una mejor manera de visualizar la misma información es haciendo una grfáica de barras horizontales ordenando de la proporción más grande a la más pequeña, el eje x tiene que ir de 0 a 100%.

Si no te queda de otra más que presentar un pie, asegúrate que las proporciones suman 100%!, ordena las divisiones de mayor a menor…

¿Algo malo aquí?

* Imagen tomada de flowingdata.com

¿Qué? (╯°□°)╯︵ ┻━┻

* Imagen tomada de flowingdata.com

Paren!!!! (╯°□°)╯︵ ┻━┻

* Imagen tomada de flowingdata.com

Moraleja: Pon atención a tus visualizaciones son TAN IMPORTANTES como los modelos que realizas, es tu responsabilidad presentar información precisa, sin sesgo y que permita a los demás tomar decisiones basadas en ellas.

Como NO hacer una gráfica de barras

* Imagen tomada de viz.wtf

No hagan esto!!!! (╯°□°)╯︵ ┻━┻ \(\rightarrow\) Por eso en esta clase están prohibidos los pie!!!

* Imagen tomada de viz.wtf

Tipos de variables

  • Datos Numéricos
    • Numéricas continuas: métricas, …
    • Numéricos discretos: conteos, enteros
  • Datos categóricos
    • Categóricas nominales
    • Categóricas ordinales -aunque muchas veces pueden ser tomados como numéricos-
  • Texto, audio, imágenes

EDA no gráfico

Univariado

Datos categóricos

¿Qué podemos encontrar?

  • Valores faltantes
  • Proporciones
  • Frecuencias

\(\rightarrow\) Generar una tabla de frecuencias de cada categoría. Por ejemplo:

category count proportion
girls 2 0.1333333
boys 4 0.2666667
women 3 0.2000000
man 6 0.4000000
total 15 1.0000000

Es muy importante obtener los totales para identificar errores, faltantes, anomalías en los datos

Datos numéricos

Quisieramos conocer algunas métricas de centralidad: modalidad (número de modas), mediana, media; dispersión: desviación estándar, forma -distribución teórica- para identificar si tiene colas pesadas: *skewness -medida de asimetría- e identificación de outliers.

Skewness: valores cercanos a 0 indican muy poco skewness, si el número es negativo la cola es a la izquierda, si el número es positivo la cola es a la derecha


La mayoría de estas métricas las puedes obtener con un summary en R. Por ejemplo:

data(mtcars)

glimpse(mtcars)
## Observations: 32
## Variables: 11
## $ mpg  <dbl> 21.0, 21.0, 22.8, 21.4, 18.7, 18.1, 14.3, 24.4, 22.8, 19....
## $ cyl  <dbl> 6, 6, 4, 6, 8, 6, 8, 4, 4, 6, 6, 8, 8, 8, 8, 8, 8, 4, 4, ...
## $ disp <dbl> 160.0, 160.0, 108.0, 258.0, 360.0, 225.0, 360.0, 146.7, 1...
## $ hp   <dbl> 110, 110, 93, 110, 175, 105, 245, 62, 95, 123, 123, 180, ...
## $ drat <dbl> 3.90, 3.90, 3.85, 3.08, 3.15, 2.76, 3.21, 3.69, 3.92, 3.9...
## $ wt   <dbl> 2.620, 2.875, 2.320, 3.215, 3.440, 3.460, 3.570, 3.190, 3...
## $ qsec <dbl> 16.46, 17.02, 18.61, 19.44, 17.02, 20.22, 15.84, 20.00, 2...
## $ vs   <dbl> 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, ...
## $ am   <dbl> 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, ...
## $ gear <dbl> 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, ...
## $ carb <dbl> 4, 4, 1, 1, 2, 1, 4, 2, 2, 4, 4, 3, 3, 3, 4, 4, 4, 1, 2, ...
summary(mtcars)
##       mpg             cyl             disp             hp       
##  Min.   :10.40   Min.   :4.000   Min.   : 71.1   Min.   : 52.0  
##  1st Qu.:15.43   1st Qu.:4.000   1st Qu.:120.8   1st Qu.: 96.5  
##  Median :19.20   Median :6.000   Median :196.3   Median :123.0  
##  Mean   :20.09   Mean   :6.188   Mean   :230.7   Mean   :146.7  
##  3rd Qu.:22.80   3rd Qu.:8.000   3rd Qu.:326.0   3rd Qu.:180.0  
##  Max.   :33.90   Max.   :8.000   Max.   :472.0   Max.   :335.0  
##       drat             wt             qsec             vs        
##  Min.   :2.760   Min.   :1.513   Min.   :14.50   Min.   :0.0000  
##  1st Qu.:3.080   1st Qu.:2.581   1st Qu.:16.89   1st Qu.:0.0000  
##  Median :3.695   Median :3.325   Median :17.71   Median :0.0000  
##  Mean   :3.597   Mean   :3.217   Mean   :17.85   Mean   :0.4375  
##  3rd Qu.:3.920   3rd Qu.:3.610   3rd Qu.:18.90   3rd Qu.:1.0000  
##  Max.   :4.930   Max.   :5.424   Max.   :22.90   Max.   :1.0000  
##        am              gear            carb      
##  Min.   :0.0000   Min.   :3.000   Min.   :1.000  
##  1st Qu.:0.0000   1st Qu.:3.000   1st Qu.:2.000  
##  Median :0.0000   Median :4.000   Median :2.000  
##  Mean   :0.4062   Mean   :3.688   Mean   :2.812  
##  3rd Qu.:1.0000   3rd Qu.:4.000   3rd Qu.:4.000  
##  Max.   :1.0000   Max.   :5.000   Max.   :8.000

Multivariado

En el análisis multivariado lo que queremos encontrar son relaciones entre varias columnas -normalmente 2-

Datos categóricos
  • Cuando queremos comparar 2 variables (con pocas categorías) se puede ocupar un cross-tabulation donde los valores de una variable se ponen en las columans y los valores de la otra variable en renglones para armar una matriz de frecuencias.

Por ejemplo:

Edad/Sexo Mujer Hombre Total
Jóven 2 3 5
Adulto 3 5 8
Adulto mayor 4 2 6
Total 9 10 19
Datos numéricos

Lo más común es obtener la correlación y la covarianza entre 2 variables numéricas. Con la covarianza queremos ver qué tanto cambia una variable si la otra lo hace.

Es más sencillo identificar estos cambios con la correlación, ya que esta va de [-1,1] donde -1 indica que las variables tiene una correlación lineal perfecta negativa, 1 indica que las variables tiene una correlación lineal perfecta positiva y 0 que las variables no tienen correlación. De nuevo, estas métricas son más sencillas de analizar visualmente-.

Cuando se tienen más de 2 variables numéricas normalmente se realizan matrices de covarianzas y correlaciones.

GEDA

Datos Numéricos

¿Qué podríamos encontrar?

  • Simetría/Asimetría
  • Huecos
  • Outliers
  • Multimodalidad
  • Amontonamientos (heaping)
  • Redondeos
  • Imposibilidades
  • Errores

¿Cómo podríamos visualizar estas características?

  • Histograma: Simetría/Asimetría, huecos, multimodalidad, aproximación a una distribución empírica
  • Diagrama de caja y brazos -boxplot-: outliers, amontonamientos
  • Scatterplot: Cada dato es un punto, permite identificar huecos
  • Rugplot: Gráfica que agrega pequeñas líneas verticales (eje x) u horizontales (eje y), se ocupa como un extra a un scatterplot, pero solo se recomienda cuando se tienen pocos datos \(\rightarrow\) veremos ejemplos de esta gráfica
  • Density estimate: Como un modelo de tus datos… solo ten cuidado con los límites de las variables
  • Distribution estimate: Permite comparar distribuciones como por ejemplo si una está adelante de otra
  • QQ-plot: Permite comparar dos distribuciones, la tuya vs una teórica (por default la normal)

Ejemplo

El siguiente histograma corresponde a las velocidades del camponato mundial de ski del 2011, ¿qué puedes decir de la gráfica?


Con el siguiente histograma ¿cambia en algo la historia/conclusión/opinión que tenías con el histograma anterior?


es por esto que debes hacer un análisis exploratorio profundo, ver todos los puntos de vista posibles -diferentes gráficas, diferentes análisis-

Otro ejemplo

¿Qué puedes decir de esta gráfica?


¿Y ahora?

Otro ejemplo

Utilizaremos el set de datos de precios de vivienda de Boston que viene en el paquete MASS, este set de datos contiene 14 variables y 506 observaciones de áreas alrededor de la ciudad de Boston.

  • crim: per capita crime rate by town
  • zn: proportion of residential land zoned for lots over 25,000 sq ft
  • indus: proportion of non-retail business acres per town
  • chas: Charles River dummy variable (=1 if tract bound river; 0 otherwise)
  • nox: nitrogen oxiides concentration (parts per 10 million)
  • rm: average number of rooms per dwelling
  • age: proportion of onwer-occupied units built prior to 1940
  • dis: weighted mean of distances to fice Boston employment centres
  • rad: index of accessibility to radial highways
  • tax: full-value propoerty-tax rate per \$10,000
  • ptratio: pupil-teacher ratio by town
  • black: proportion of blacks by town ????
  • lstat: lower status of the population (percent)
  • medv: median value of owner-occupied homes in \$1000

En particular nos interesa revisar la varaible medv

  • Empecemos sin visualización, generemos una tabla con los diferentes valores de medv para ver qué información podemos encontrar
table(Boston$medv)
## 
##    5  5.6  6.3    7  7.2  7.4  7.5  8.1  8.3  8.4  8.5  8.7  8.8  9.5  9.6 
##    2    1    1    2    3    1    1    1    2    2    2    1    2    1    1 
##  9.7 10.2 10.4 10.5 10.8 10.9   11 11.3 11.5 11.7 11.8 11.9   12 12.1 12.3 
##    1    3    2    2    1    2    1    1    1    2    2    2    1    1    1 
## 12.5 12.6 12.7 12.8   13 13.1 13.2 13.3 13.4 13.5 13.6 13.8 13.9   14 14.1 
##    1    1    3    1    1    4    1    3    4    2    2    5    2    1    3 
## 14.2 14.3 14.4 14.5 14.6 14.8 14.9   15 15.1 15.2 15.3 15.4 15.6 15.7   16 
##    1    2    2    3    2    1    3    3    1    3    1    2    5    1    1 
## 16.1 16.2 16.3 16.4 16.5 16.6 16.7 16.8   17 17.1 17.2 17.3 17.4 17.5 17.6 
##    3    2    1    1    2    2    2    2    1    3    3    1    3    3    1 
## 17.7 17.8 17.9   18 18.1 18.2 18.3 18.4 18.5 18.6 18.7 18.8 18.9   19 19.1 
##    1    5    1    1    1    3    2    3    4    2    3    2    4    2    4 
## 19.2 19.3 19.4 19.5 19.6 19.7 19.8 19.9   20 20.1 20.2 20.3 20.4 20.5 20.6 
##    2    5    6    4    5    2    3    4    5    5    2    4    4    3    6 
## 20.7 20.8 20.9   21 21.1 21.2 21.4 21.5 21.6 21.7 21.8 21.9   22 22.1 22.2 
##    2    3    2    3    2    5    5    2    2    7    2    3    7    1    5 
## 22.3 22.4 22.5 22.6 22.7 22.8 22.9   23 23.1 23.2 23.3 23.4 23.5 23.6 23.7 
##    2    2    3    5    2    4    4    4    7    4    4    2    1    2    4 
## 23.8 23.9   24 24.1 24.2 24.3 24.4 24.5 24.6 24.7 24.8   25 25.1 25.2 25.3 
##    4    5    2    3    1    3    4    3    2    3    4    8    1    1    1 
## 26.2 26.4 26.5 26.6 26.7   27 27.1 27.5 27.9   28 28.1 28.2 28.4 28.5 28.6 
##    1    2    1    3    1    1    2    4    2    1    1    1    2    1    1 
## 28.7   29 29.1 29.4 29.6 29.8 29.9 30.1 30.3 30.5 30.7 30.8   31 31.1 31.2 
##    3    2    2    1    2    2    1    3    1    1    1    1    1    1    1 
## 31.5 31.6 31.7   32 32.2 32.4 32.5 32.7 32.9   33 33.1 33.2 33.3 33.4 33.8 
##    2    2    1    2    1    1    1    1    1    1    2    2    1    2    1 
## 34.6 34.7 34.9 35.1 35.2 35.4   36 36.1 36.2 36.4 36.5   37 37.2 37.3 37.6 
##    1    1    3    1    1    2    1    1    2    1    1    1    1    1    1 
## 37.9 38.7 39.8 41.3 41.7 42.3 42.8 43.1 43.5 43.8   44 44.8 45.4   46 46.7 
##    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1 
## 48.3 48.5 48.8   50 
##    1    1    1   16

Espero que no pienses diferente… pero tratar de sacar información de esta tabla esta difícil, solo podemos ver rápidamente que todos los números están redondeados a 1 decimal, fuera de eso… está complicado. Mejor visualizemos…

ggplot(Boston, aes(x=medv)) + 
  geom_histogram() + 
  theme_bw() + 
  ggtitle("Valor medio de las casas (en miles de dólares")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Además del warning que regresa R al generar el histograma ¿qué información puedes extraer?

  • ¿Qué habrá pasado cuándo el valor medv anda en 25? Se ve una caída interesante
  • El valor de medv parece inusual …

¿Qué pasa si cambiamos el tamaño de los bines?

ggplot(Boston, aes(x=medv)) + 
  geom_histogram(binwidth=1) + 
  theme_bw() + 
  ggtitle("Valor medio de las casas (en miles de dólares")

Hay detalles que antes no se percibían, como que en 34 hay una caída que no se había identificado antes

Ahora veamos todas las variables:

B2 <- gather(Boston, bos_vars, bos_values, crim:medv)
ggplot(B2, aes(bos_values)) +
    geom_histogram() + 
    theme_bw() + 
    facet_wrap(~ bos_vars , scales = "free")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Este es un buen punto de partida, pero el tamaño de los bines no beneficia a todas las variables, cada variable debe tener su tamaño de bin adecuado, por otro lado la escala varía mucho entre variables: hay unas que van de 0 a 20 y otras de 0 a 400.

Ejercicio

  1. Utiliza la variable medv para generar las siguientes gráficas en R: ¿Qué observas en cada una de estas gráficas?
  • ?boxplot
  • ?stripchart Un scatterplot de 1 dimensión, puede ser una alternativa al boxplot cuando tienes pocos datos
  • ?stem
  • ?density Necesitarás agregar un plot para ver la gráfica
  • ?rug Primero necesitarás generar o el stripchart o el density para agregarle rug
#boxplot
ggplot(Boston, aes(x="var", y=medv)) +
  geom_boxplot() +
  theme_bw() + 
  coord_flip() +
  ggtitle("boxplot variable medv")

#stripchart
stripchart(Boston$medv)

#stem
stem(Boston$medv)
## 
##   The decimal point is at the |
## 
##    4 | 006
##    6 | 30022245
##    8 | 1334455788567
##   10 | 2224455899035778899
##   12 | 013567778011112333444455668888899
##   14 | 0111233445556689990001222344666667
##   16 | 01112234556677880111222344455567888889
##   18 | 01222334445555667778899990011112233333444444555566666778889999
##   20 | 0000011111223333444455566666677888990001122222444445566777777788999
##   22 | 00000001222223344555666667788889999000011111112222333344566777788889
##   24 | 001112333444455566777888800000000123
##   26 | 24456667011555599
##   28 | 01244567770011466889
##   30 | 111357801255667
##   32 | 0024579011223448
##   34 | 679991244
##   36 | 01224502369
##   38 | 78
##   40 | 37
##   42 | 38158
##   44 | 084
##   46 | 07
##   48 | 358
##   50 | 0000000000000000
#density
plot(density(Boston$medv))

#rug
rug(Boston$medv)

  1. En la gráfica de las 14 variables mostrada arriba ¿Cómo describirías las distribuciones? ¿Para cuales variables sería mejor utilizar boxplot? ¿Por qué?
ggplot(Boston, aes(x="var", y=black)) +
  geom_boxplot() +
  theme_bw() +
  coord_flip() + 
  ggtitle("Boxplot variable black")

ggplot(Boston, aes(x="var", y=crim)) +
  geom_boxplot() +
  theme_bw() +
  coord_flip() + 
  ggtitle("Boxplot variable crim")

ggplot(Boston, aes(x="var", y=tax)) +
  geom_boxplot() +
  theme_bw() +
  coord_flip() + 
  ggtitle("Boxplot variable tax")

ggplot(Boston, aes(x="var", y=zn)) +
  geom_boxplot() +
  theme_bw() +
  coord_flip() + 
  ggtitle("Boxplot variable zn")

Otro ejemplo

Veamos ahora un poco de outliers con un dataset de películas que viene en el paquete ggplot2movies -antes formaba parte del paquete ggplot pero se hizo su propio paquete para bajar el overhead de bajar ggplot2-

library(ggplot2movies)
library(dplyr)
library(scales)
library(plotly)

data(movies)

glimpse(movies) 
## Observations: 58,788
## Variables: 24
## $ title       <chr> "$", "$1000 a Touchdown", "$21 a Day Once a Month"...
## $ year        <int> 1971, 1939, 1941, 1996, 1975, 2000, 2002, 2002, 19...
## $ length      <int> 121, 71, 7, 70, 71, 91, 93, 25, 97, 61, 99, 96, 10...
## $ budget      <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
## $ rating      <dbl> 6.4, 6.0, 8.2, 8.2, 3.4, 4.3, 5.3, 6.7, 6.6, 6.0, ...
## $ votes       <int> 348, 20, 5, 6, 17, 45, 200, 24, 18, 51, 23, 53, 44...
## $ r1          <dbl> 4.5, 0.0, 0.0, 14.5, 24.5, 4.5, 4.5, 4.5, 4.5, 4.5...
## $ r2          <dbl> 4.5, 14.5, 0.0, 0.0, 4.5, 4.5, 0.0, 4.5, 4.5, 0.0,...
## $ r3          <dbl> 4.5, 4.5, 0.0, 0.0, 0.0, 4.5, 4.5, 4.5, 4.5, 4.5, ...
## $ r4          <dbl> 4.5, 24.5, 0.0, 0.0, 14.5, 14.5, 4.5, 4.5, 0.0, 4....
## $ r5          <dbl> 14.5, 14.5, 0.0, 0.0, 14.5, 14.5, 24.5, 4.5, 0.0, ...
## $ r6          <dbl> 24.5, 14.5, 24.5, 0.0, 4.5, 14.5, 24.5, 14.5, 0.0,...
## $ r7          <dbl> 24.5, 14.5, 0.0, 0.0, 0.0, 4.5, 14.5, 14.5, 34.5, ...
## $ r8          <dbl> 14.5, 4.5, 44.5, 0.0, 0.0, 4.5, 4.5, 14.5, 14.5, 4...
## $ r9          <dbl> 4.5, 4.5, 24.5, 34.5, 0.0, 14.5, 4.5, 4.5, 4.5, 4....
## $ r10         <dbl> 4.5, 14.5, 24.5, 45.5, 24.5, 14.5, 14.5, 14.5, 24....
## $ mpaa        <chr> "", "", "", "", "", "", "R", "", "", "", "", "", "...
## $ Action      <int> 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,...
## $ Animation   <int> 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## $ Comedy      <int> 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,...
## $ Drama       <int> 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1,...
## $ Documentary <int> 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## $ Romance     <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## $ Short       <int> 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0,...
## histograma
ggplot(movies, aes(x=length)) + 
  geom_histogram() +
  scale_y_continuous(label=comma) + 
  scale_x_continuous(label=comma) + 
  theme_bw()

¯\(º_o)/¯ chanclas… no dice mucho la gráfica… ¿o si? y además, ¿por qué sale más de 4,000 en longitud de la película? veamos qué hay ahí

movies %>% 
filter(length > 2000) %>% 
arrange(desc(length))

Para encontrar estos outliers es más sencillo hacer diagramas de caja y brazos

p <-ggplot(movies, aes(x="var", y=length)) +
  geom_boxplot() +
  scale_x_discrete(breaks=NULL) +
  scale_y_continuous(label=comma) +
  theme_bw() + 
  coord_flip()

p 

#o con plotly para saber los numeros :P
ggplotly(p)

Puedes obtener los outilers (valores) con la función boxplot

outliers_info <- boxplot(movies$length)

summary(outliers_info) # las cosas que te devuelve el outlier 
##       Length Class   Mode     
## stats     5  integer numeric  
## n         1  -none-  numeric  
## conf      2  -none-  numeric  
## out   10698  -none-  numeric  
## group 10698  -none-  numeric  
## names     1  -none-  character
# cuantos 
format(outliers_info$out %>% length(), big.mark=",")
## [1] "10,698"

Veamos cómo se ve el histograma si quitamos estos valores atípicos

ggplot(movies, aes(x=length)) + 
    xlim(0, 180) +
    geom_histogram(binwidth = 1) +
    xlab("Duración de películas en minutos") + 
    theme_bw()

que diferencia!

Ejercicio 1/Tarea 3 A entregar el lunes 9 de octubre 2017 en tu carpeta dentro de carpeta alumno con el nombre ejercicio_eda_1 (entregar Rmd y html)

  1. ¿Qué puedes decir de esta gráfica?
  2. ¿Cómo la modificas para agregar más ticks?
  3. Haz una gráfica que muestre que los picos de 7 y 90 minutos existían antes y después de 1980
  4. Existe la varaible short que indica si una película es “corta”, ¿Qué gráfica puedes hacer para identificar el criterio que se ocupó para definir esta variable y cuáles están mal clasificadas?

Datos categóricos

¿Qué podemos encontrar?

  • Patrones insesperados en los resultados
  • Malas Distribuciones
  • Categorías extras
  • Experimentos no balanceados
  • Muchas categorías
  • “No sé”, Errores, Faltantes

¿Cómo podríamos visualizar estas características?

  • Barchars: Nominales, Variables discretas
  • Explora (o ten cuidado con) diferentes ordenamientos
  • Es posible que datos ordinales entren también en este análisis

Bivariado contínuo

¿Qué podemos encontrar?

  • Relaciones lineales y no lineales
  • Asociaciones
  • Outliers: Se puede ser outlier en una dimensión y no serlo en dos, o viceversa
  • Clusters
  • Huecos
  • Barreras
  • Relación condicional

Modelos y pruebas

  • Correlación
  • Smoothing
  • Regresión lineal \(\rightarrow\) Verifica los supuestos!!!!

¿Regresión lineal? … residuales

Ejemplos

Ahora veamos la varaible rating que representa el promedio de calificaciones de IMDB y la variable votes que representa el número de personas que calificaron la película

ggplot(movies, aes(x=votes, y=rating)) +
  geom_point() +
  ylim(1,10) + 
  scale_x_continuous(label=comma) + 
  theme_bw()

Ejercicio 2/Tarea 3 A entregar el lunes 9 de octubre 2017 en tu carpeta dentro de carpeta alumno en el mismo archivo creado en el ejercicio 1

  1. Agrega alpha-blending ¿Qué pasa con los outliers? ¿Diferentes valores funcionan mejor?
  2. ¿Cómo se ve la gráfica si remueves las películas con menos de 100 votos?
  3. ¿Cómo se ve la gráfica si remueves todas las películas que tienen un rating arriba de 9?

Es posible estudiar posibles modelos (al igual que en el caso univariado) por ejemplo, ocupando el set de datos Cars93 en el paquete MASS:

data("Cars93")

ggplot(Cars93, aes(x=Weight, y=MPG.city)) +
  geom_smooth(colour="green") +
  ylim(0, 50) + 
  scale_x_continuous(label=comma) +
  geom_point() +
  theme_bw() 

Ejercicio 3/Tarea 3

  1. ¿Cuál es el outlier de la izquierda?
  2. En muchos países en lugar de medirse en millas por galón, se mide en litros por 100 km. ¿Qué pasa si graficas MPG.city contra Horsepower? ¿Existe una relación lineal? ¿Cuáles son los outliers?

También se podemos hacer una matriz de scatterplots -splom (como lo hicimos con los histogramas :)), para ello ocupamos el método ggpairs de la librería GGally en el dataset de precios de vivienda ade Boston.

library(GGally)


dplyr::select(Boston, -rad, -chas) %>% 
ggpairs(title="Boston dataset", diag=list(continuous="density", axisLabels='none'))

Ejercicio 4/Tarea 3

  1. ¿Cuáles están positivamente correlacionados con medv?
  2. La variable crim -tasa de crímenes per cápita- tiene scatterplots con forma inusual, donde los valores altos de crim solo ocurren para un valor de la otra variable ¿Qué explicación le puedes dar?
  3. Hay varias formas en los scatterplots, escoge 5 y explica cómo las interpretas

Más de 2 variables continuas

Para ver variables variables continuas se puede ocupar el parallel coordinate plot pcp, estos diagramas permiten dar un vista rápida a las distribuciones univariadas de varias variables a la vez: si son skew, si hay outliers, si hay gaps, etc.

Ahora utilizaremos el dataset iris para probar estas gráficas utilziando de la librería GGally el método ggparcoord

data(iris)

ggparcoord(iris, columns=1:4, groupColumn = "Species") 

Esta está más difícil de interpretar…

En un pcp cada línea es una observación del dataset, y cada atributo/variable del set es un punto en la gráfica.

Lo que uno observa en la gráfica depende del orden en el cuál se dibujan los ejes. Hay autores que sugieren intentar varias combinaciones o inclusive poner varias copias de los ejes. Quizá lo mejor sea tener una gráfica interactiva para tal efecto… lo veremos más adelante.

Este tipo de gráficas se puede usar para comparar modelos, series de tiempo, análisis de clusters, índices, etc. Esto lo discutiremos más adelante en el curso. Lo que se busca al ocupar esta gráfica es encontrar similitudes al comparar diferentes características del dataset.

Hay varias cosas que ajustar en estas gráficas para poder ser interpretadas: el orden de las variables y el escalamiento de los datos.

  • Escalamiento: Cuando vamos a comparar diferenes mediciones tenemos que determinar alguna manera de ponerlos en la misma escala. En las gráficas pcp por default, la escala depende del máximo y mínimo valor encontrado por cada variable, el mínimo a \(0\) y el máximo se maperará a \(1\) (o 0% y 100%)

\[y_{ij}=\frac{x_{ij} - \min_i x_{ij}}{\max_i x_{ij} - \min_i x_{ij}}\]

Otra forma de escalar los datos es usando la desviación estándar o el rango intercuantil (IQR), por ejemplo:

\[z_{ij}=\frac{x_{ij} - \bar{x}_j}{sd(x_j)}\]

Este es el escalamiento por default que ocupa ggparcoord.

La escala de cada variable es propia a esa variable, es decir, no se deben comparar las alturas entre diferentes variables (diferentes puntos en el eje x)

iris1 <- iris
names(iris1) <- c(abbreviate(names(iris)[1:4]), "Species")
a1 <- ggparcoord(iris1, columns = 1:4, 
                 alphaLines = 0.7,  
                 groupColumn = "Species") + 
  ggtitle("a1")
a2 <- ggparcoord(iris1, columns = 1:4, 
                 scale="uniminmax", 
                 alphaLines=0.7, 
                 groupColumn = "Species") + 
  ggtitle("a2")
a3 <- ggparcoord(iris1, columns = 1:4, 
                 scale="globalminmax", 
                 alphaLines=0.7, 
                 groupColumn = "Species") + 
  ggtitle("a3")
a4 <- ggparcoord(iris1, columns = 1:4, 
                 scale="center", 
                 scaleSummary="median", 
                 alphaLines=0.7, 
                 groupColumn = "Species") +
  ggtitle("a4")

gridExtra::grid.arrange(a1, a2, a3, a4)

Ejercicio 5/Tarea 3

  1. Usando el dataset Boston realiza un pcp, intenta resaltar las características que haz observado en los ejercicios anteriores. Piensa cómo le hiciste…

Varias variables categóricas

Cuando queremos comparar varias variables categóricas al mismo tiempo tenemos el problema de que haya muchas categorías por variable y la gran cantidad de posibles ordenamientoso de las variables. Por ejemplo, para \(J\) variables categóricas con \(c\) número de categorías, las variables pueden ser ordenadas de \(J!\) maneras diferentes y las categorías dentro de las variables de \(\Pi_{j=1}^J c_j!\), lo cual da un total de \(J!\Pi_{j=1}^Jc_j!\) ordenamientos, o sea un montón…

Los tipos de gráficas que podemos ocupar en estas situaciones son:

  • mosaicplots
  • doubledecker plots
  • fluctuation diagrams
  • treemaps
  • association plots
  • parallel sets/categorical parallel coordinate plots

New approaches in Visualization of Categorical Data: R Package extracat de A. Pilhöfer y A. Unwin. JSS, Vol53 issue 7, May 2013.

Doubledecker

En esta gráfica las variables se dividen en variables explicativas y variable objetivo, esta última ocupa el eje vertical

library(vcd)

data(Titanic)

#variables a ocupar para hacer una tabla de contingencia, la variable tardet se pone al final
doubledecker(Survived ~ Sex, data=Titanic, gp=gpar(fill=c("grey90", "darkblue")))

doubledecker(Survived ~ Class, data=Titanic, gp=gpar(fill=c("grey90", "darkblue")))


Conforme más variables agregamos se vuelve más difícil la interpretación:

doubledecker(Survived ~ Sex + Class, data=Titanic, gp=gpar(fill=c("grey90", "darkblue")))


Además, como mencionábamos, al ser una tabla de tipo mosaic la construcción de la gráfica depende del orden de las variables

doubledecker(Survived ~ Class + Sex, data=Titanic, gp=gpar(fill=c("grey90", "darkblue")))


Mosaicplot

En este tipo de gráfica ocupamos rectángulos proporcionales a los conteos de las combinaciones que los rectángulos representan. Se dibujan partiendo con un rectángulo que representa todo el dataset, luego se toma la primer variable y se parte el eje horizontal en secciones proporcionales a los tamaños de las categorías.

titanic <- as.data.frame(Titanic)
par(mfrow=c(2,2),  mar= c(4, 4, 0.1, 0.1))
mosaicplot(xtabs(Freq ~ Survived, data=titanic), main="")
mosaicplot(xtabs(Freq ~ Survived + Sex, data=titanic), main="")
mosaicplot(xtabs(Freq ~ Survived + Sex + Class, data=titanic), main="")
mosaicplot(xtabs(Freq ~ Survived + Sex + Class + Age, data=titanic), main="")

De nuevo el orden de las variables determinará la apariencia (y tal vez el insight que puedes obtener)

Otra opción es utilizar pairs

pairs(xtabs(Freq ~ ., data=titanic))

La diagonal de esta gráfica contiene gráficas de barra para las variables individuales y mosaicplots en los elementos que no están en la diagonal. Si hay muchas variables esta gráfica será muy difícil de leer.

Si existe una variable a explicar obvia (en nuestro caso Survived), quizá sea mejor tomar esto en cuenta en nuestro análisis:

ggplot(titanic, aes(Survived, Freq, fill=Sex)) + 
    geom_bar(stat = "identity") +
    theme_bw() +
    facet_grid(Class ~ Sex + Age) + theme(legend.position="none")

Fluctuation diagrams

Si queremos observar tablas de contingencia muy grandes o matrices de confusión, podemos usar las gráficas de tipo fluctile. En particular estas gráficas resaltan qué subgrupos aparecen más frecuentemente, o cuales combinaciones no aparecen en lo absoluto.

library(extracat)

#nota que requiere una tabla de entrada! no un data frame
fluctile(Titanic)

Por otro lado, si solo nos interesa comparar las tasas, podemos usar la funcion rmb -relative multiple barchar- con el parámetro freq.trans="const":

rmb(formula = ~ Sex + Class + Age + Survived, 
              data=titanic, 
              cat.ord=2, spine=TRUE, freq.trans="const")

En esta gráfica vemos las tasas de supervivencia (el color verde) por subgrupo. Esto sería muy difícil de apreciar con un mosaicplot normal.

Las rmb mezclan las gráficas de barras y los mosaicplots.

Forma deseable de los datos

Para muchos de los análisis de datos requerimos que los datos estén en formato tidy

Tidy Data

Tidy Data

Fuente: R for Data Science, Wickham and Grolemund, 2016

Es decir:

  1. Cada variable una columna
  2. Cada observación un renglón
  3. Cada valor una celda

Fuente: Presentaciones de Hadley Wickham

Formatos salvajes

Los siguientes ejemplos de dataset son típicos de algunas fuentes de datos, por ejemplo: INEGI (al menos en formatos al 2014)

Fecha implícita
Lat Long Indicador
Obs1 # # #
Otro con Fecha implícita
lugar indicador
obs 1
obs 2
Implícita la variable, ¿¿por qué?? (╯°□°)╯︵ ┻━┻
Fecha 1 Fecha 2
lugar 1
lugar 2
Fecha 1 Fecha 2
LUGAR 1
Ind 1 # #
Ind 2 # #
LUGAR 2
Ind 1 # #
Ind 2 # #

O cosas más locas!

Indicador 1 Indicador 2
Fecha 1 Fecha 2 Fecha 1 Fecha 2
lugar 1 Ind 1 Ind 1 Ind 2 Ind 2
lugar 1 Ind 1 Ind 1 Ind 2 Ind 2

Otro ejemplos:

  • Nombres de las columnas representan valores de los datos en lugar de nombres de variables -el nombre de un lugar por ejemplo-
  • Una columna contiene varias variables en lugar de una variable
  • Una tabla contiene más de una unidad de observación
  • Las variables están contenidas en los renglones y columnas, en lugar de sólo columnas.
  • Los datos de una unidad observacional están dispersas en varios data sets

Ejemplos:

library(tidyr)

messy <- data.frame(nombre=c("juan.perez.lopez","martha.lopez.benitez",
                             "jesus.ramirez.perez","jose.martinez.lopez",
                             "aurora.saldivar.salazar"),
                    genero_edad=c("m.35","f.23","m.30","m.25","f.33"),
                    time=c(1,3,4,5,6))

messy

Tendríamos que dejarlo tidy:

  • Separando el nombre
semi_messy <- messy %>% separate(col=nombre, into=c("nombre",
                                      "apellido_paterno",
                                      "apellido_materno"), 
                   sep="\\.")

semi_messy
  • Separando género y edad
clean <- semi_messy %>% separate(col=genero_edad, into=c("genero","edad"), 
                   sep="\\.")

clean

Otro ejemplo: ¿Qué está mal?

messy <- data.frame(pais=c(rep("Afganistan",4),
                           rep("Brazil",4),
                           rep("China",4)),
                    year=c(rep(1999,2),rep(2000,2),
                           rep(1999,2),rep(2000,2),
                           rep(1999,2),rep(2000,2)),
                    llave=c("casos","poblacion","casos","poblacion",
                          "casos","poblacion","casos","poblacion",
                          "casos","poblacion","casos","poblacion"),
                    valor=c(75,1300000,134,1400000,
                            10000,100000000,12000,120000000,
                            56000,150000000,60000,170000000))

messy

Lo deberíamos arreglar con:

clean <- messy %>% spread(key=llave, value=valor, fill=NA)

clean

El último

stocks <- data_frame(
  time = as.Date('2009-01-01') + 0:9,
  X = rnorm(10, 0, 1),
  Y = rnorm(10, 0, 2),
  Z = rnorm(10, 0, 4)
)

stocks
stocks %>% gather(stock, price, -time)

Casos de estudio

Reproducibilidad

En estos casos de estudio nos vamos a encontrar con nuestro primer tipo de pipeline, en este caso en particular, este pipeline no es para ejecutar grandes volúmenes de datos o para ejecutar contínuamente, sino para poder reproducir el proceso de exploración y modelado de datos.

Antes de empezar te recomiendo ampliamente que utilices packrat para la administración de paquetes en R packrat

En tu carpeta crea las carpetas german y algas dentro de ellas crea los archivos:

  • 00-load.R
  • 01-prepare.R
  • 02-clean.R
  • run.R

En estos archivos pondrás código para ejecutar los pipelines de los siguientes casos de estudio

German

¿Quién eres?

Eres el científico de datos de un banco alemán, el banco tiene muchas pérdidas debido a malos créditos y quiere reducirlas. Te piden realizar esta tarea, indicando que quieren reducir la tasa de pérdidas en un 10%.

Neceistaras lo siguiente:

rm(list = ls())

instalar <- function(paquete) {

    if (!require(paquete,character.only = TRUE, quietly = TRUE, warn.conflicts = FALSE)) {
        install.packages(as.character(paquete), dependecies = TRUE, repos = "http://cran.us.r-project.org")
        library(paquete, character.only = TRUE, quietly = TRUE, warn.conflicts = FALSE)
    }
}

paquetes <- c('lubridate', 'magrittr', 'ggvis', 'dplyr', 'tidyr', 'readr', 'rvest', 
              'ggplot2', 'stringr', 'ggthemes', 'googleVis', 'shiny', 'tibble', 'vcd', 'vcdExtra',
              'GGally', 'readODS', 'readxl', "RSQLite")

lapply(paquetes, instalar);

source("metadata.R")
source("utils.R")

Datos

Usaremos para este ejemplo, los datos de crédito alemán (German data set). German Credit Data

Carga de datos

german_url <- paste0('http://archive.ics.uci.edu/ml',
                    '/machine-learning-databases/statlog',
                    '/german/german.data')
german_data <- read_delim(german_url, 
                          col_names=F,
                          delim=" ")

Tenemos 1,000 observaciones y 21 variables. Veamos cómo están los datos:

head(german_data)

¿Qué? (╯°□°)╯︵ ┻━┻

Ejercicio

  • Crea una función load en utils.R en tu carpeta que descargue si y solo si no existe un archivo german.rds, si no existe descarga y guarda el archivo.
  • ?saveRDS, ?readRDS, ?file.exists

Transformación de datos

Los nombres de las columnas fueron copiados a mano desde german.doc, los nombres se encuentran en el archivo metadata.R

source("utils.R")

german_colnames
##  [1] "Status of existing checking account"                     
##  [2] "Duration in month"                                       
##  [3] "Credit history"                                          
##  [4] "Purpose"                                                 
##  [5] "Credit amount"                                           
##  [6] "Savings account/bonds"                                   
##  [7] "Present employment since"                                
##  [8] "Installment rate in percentage of disposable income"     
##  [9] "Personal status and sex"                                 
## [10] "Other debtors / guarantors"                              
## [11] "Present residence since"                                 
## [12] "Property"                                                
## [13] "Age in years"                                            
## [14] "Other installment plans"                                 
## [15] "Housing"                                                 
## [16] "Number of existing credits at this bank"                 
## [17] "Job"                                                     
## [18] "Number of people being liable to provide maintenance for"
## [19] "Telephone"                                               
## [20] "foreign worker"                                          
## [21] "good_loan"

La variable de salida la definimos como categórica (factor en R)

colnames(german_data) <- german_colnames

german_data$good_loan <- as.factor(
                          ifelse(
                            german_data$good_loan == 1, 
                            'GoodLoan', 
                            'BadLoan'
                            )
                          )

Decodificar

  • Crea una función german_decode en un archivo utils.R dentro de tu carpeta, esta función debe de utilizar german_codes (en el archivo metadata.R) para
    decodificar los elementos de todas las columnas (por ejemplo A201 -> yes)

  • Utiliza dplyr para decodificar todas las columnas de german_data

TIP: verifica el uso de left_join, rbind y cbind

german_data  <- german_data %>% 
                     mutate_all(funs(german_decode))

german_data

Datos manejables

En este momento deberás de tener archivos 00-load.R, 01-prepare.R, 02-clean.R, metadata.R y un archivo utils.R dentro de german.
Además deberías de tener un archivo german.rds.

Ejercicio

  • ¿Hay algo raro con los datos de préstamo?

  • ¿Cuál crees que debería ser la distribución del resultado del préstamo Good_Loan respecto a Credit history?

  • Grafícalo y comenta tus resultados.

  • Si lo vas a hacer con ggplot2 usa este cheatsheet o estos ejemplos

Tarea 4 Entrega lunes 16 de octubre 23:59:59 CST en tu carpeta alumnos/german

  • Dentro de tu carpeta german tu archivo utils.R, 00-load.R, 01-prepare.R, 02-clean.R y run.R. \(\rightarrow\) Si tuvo sentido para ti poner las funciones en util y nada más … entonces solo sube tu util.R -te recomiendo que aunque hayas creado las funciones ahí de todas maneras generes el archivo 00 y 01 por claridad y sanidad mental tanto tuya como la de tu equipo :)-
  • Tener la función load completa: bajar el dataset german si no existe y guardárla como rds, o si ya existe, cargarla con readRDS
  • Tener la función de german_decode: si prefieres ocupar tu propia implementación adelante -de hecho es preferible- cada quién resuelve un problema a su manera y es mejor que te quede bastante claro como solucionar este problema porque te enfrentarás a él MUCHAS veces
  • ¿Encontraste algo raro en los préstamos? ¿Qué? ¿Cómo lo encontraste?
  • ¿Cuál crees que debería ser la distribución del resultado del préstamo Good_Loan respecto a Credit history? Grafícalo y comenta tus resultados \(\rightarrow\) checa los tips de como gráficarlo con ggplot2 -te van a salir cosas raras=feas (visualmente)- pero no te estreses, las arreglaremos la siguiente clase ╭(◔ ◡ ◔)/

Ejercicio

  • Fue terrible poder hacer la gráfica con ggplot2 utilizando los nombres de columnas que pusimos (german_colnames).

  • Modifica el archivo donde tengas german_colnames (puede ser utils.R o metadata.R) y sustituye (usando quizá stringr o grep) los ' ' y '/' por '_' (ve la guía de estilo) y pasa todo a minúsculas.

  • Ejecuta todo de nuevo (¡la ventaja de ser reproducible!)

colnames(german_data) <- german_clean_colnames(german_colnames)

colnames(german_data)
##  [1] "status_of_existing_checking_account"                     
##  [2] "duration_in_month"                                       
##  [3] "credit_history"                                          
##  [4] "purpose"                                                 
##  [5] "credit_amount"                                           
##  [6] "savings_account_bonds"                                   
##  [7] "present_employment_since"                                
##  [8] "installment_rate_in_percentage_of_disposable_income"     
##  [9] "personal_status_and_sex"                                 
## [10] "other_debtors___guarantors"                              
## [11] "present_residence_since"                                 
## [12] "property"                                                
## [13] "age_in_years"                                            
## [14] "other_installment_plans"                                 
## [15] "housing"                                                 
## [16] "number_of_existing_credits_at_this_bank"                 
## [17] "job"                                                     
## [18] "number_of_people_being_liable_to_provide_maintenance_for"
## [19] "telephone"                                               
## [20] "foreign_worker"                                          
## [21] "good_loan"

Intermedio

  • Si la gráfica de barras te quedó desacomodada, este código ordena los bar charts (Adolfo de Unánue)
german_data %>% 
    group_by(credit_history) %>% 
    dplyr::summarise(count = n()) %>% 
    arrange(desc(count)) %>% 
    ggplot(.) + 
        geom_bar(aes(x=reorder(credit_history, count), y = count), stat="identity", fill="gray") + 
        coord_flip() + 
        theme_hc() + 
        ylab('casos') + 
        xlab('Historial de crédito')

Yo me lo sé pasando a factores los nombres y ordenándo los niveles como necesites presentarlos -salida de una arrange-

plot_sorted <- german_data %>% group_by(credit_history) %>%
  summarise(count=n()) %>%
  arrange(count) #como haremos coord flip necesitamos ordenarlas de manera inversa a como queremos que aparezcan en el coord_flip

plot_sorted$credit_history <- factor(plot_sorted$credit_history,
                                     levels=plot_sorted$credit_history)

ggplot(plot_sorted, aes(x=credit_history, y=count), fill="gray") +
  geom_bar(stat="identity") +
  coord_flip() +
  theme_hc() +
  ylab("casos") +
  xlab("Historial de crédito")

Sanidad de los datos

  • El nombre de la columna no significa lo que tu crees que significa

  • El significado de la columna cambia con el paso del tiempo o la metodología para medir esa variable.

  • Mucha / muy poca resolución

  • Los valores missing no son realmente faltantes (NAs), si no que significan algo
    • Regularmente no documentado
  • Si es un csv de seguro a alguien ya le pareció chistoso ponerle comas dentro de los valores de las columnas
    • Existe una historia parecida para los tsv, psv, etc.

summary

Un uso del summary() es detectar problemas en los datos.

  • ¿Valores faltantes?
    • ¿Hay una variable con muchos faltantes? ¿Por qué? ¿Es un error? ¿Significa algo?
  • ¿Valores inválidos?
    • ¿Hay negativos donde no debería de haber? (Como en edad, ingreso, estatura)
    • ¿Texto en lugar de números?
  • ¿Outliers?
    • Son aquellos valores que no crees que deberían de estar (En el ejemplo de edad 1400 años)
  • ¿Rangos?
    • Es importante saber cuanto varía la variable.
    • Si es muy amplio, puede ser un problema para algunos algoritmos de modelado.
    • Si varía muy poco (o nada) no puede ser usado como predictor.
  • ¿Unidades?
    • ¿El salario es mensual?¿Quincenal?¿Por hora?
    • ¿Los intervalos de tiempo están en segundos?¿Años?
    • ¿Las longitudes? ¿La moneda?

Ejercicio

Revisa german_data con summary(), reporta alguna anomalía.

summary(german_data)
##  status_of_existing_checking_account duration_in_month credit_history    
##  Length:1000                         Min.   : 4.0      Length:1000       
##  Class :character                    1st Qu.:12.0      Class :character  
##  Mode  :character                    Median :18.0      Mode  :character  
##                                      Mean   :20.9                        
##                                      3rd Qu.:24.0                        
##                                      Max.   :72.0                        
##    purpose          credit_amount   savings_account_bonds
##  Length:1000        Min.   :  250   Length:1000          
##  Class :character   1st Qu.: 1366   Class :character     
##  Mode  :character   Median : 2320   Mode  :character     
##                     Mean   : 3271                        
##                     3rd Qu.: 3972                        
##                     Max.   :18424                        
##  present_employment_since
##  Length:1000             
##  Class :character        
##  Mode  :character        
##                          
##                          
##                          
##  installment_rate_in_percentage_of_disposable_income
##  Min.   :1.000                                      
##  1st Qu.:2.000                                      
##  Median :3.000                                      
##  Mean   :2.973                                      
##  3rd Qu.:4.000                                      
##  Max.   :4.000                                      
##  personal_status_and_sex other_debtors___guarantors
##  Length:1000             Length:1000               
##  Class :character        Class :character          
##  Mode  :character        Mode  :character          
##                                                    
##                                                    
##                                                    
##  present_residence_since   property          age_in_years  
##  Min.   :1.000           Length:1000        Min.   :19.00  
##  1st Qu.:2.000           Class :character   1st Qu.:27.00  
##  Median :3.000           Mode  :character   Median :33.00  
##  Mean   :2.845                              Mean   :35.55  
##  3rd Qu.:4.000                              3rd Qu.:42.00  
##  Max.   :4.000                              Max.   :75.00  
##  other_installment_plans   housing         
##  Length:1000             Length:1000       
##  Class :character        Class :character  
##  Mode  :character        Mode  :character  
##                                            
##                                            
##                                            
##  number_of_existing_credits_at_this_bank     job           
##  Min.   :1.000                           Length:1000       
##  1st Qu.:1.000                           Class :character  
##  Median :1.000                           Mode  :character  
##  Mean   :1.407                                             
##  3rd Qu.:2.000                                             
##  Max.   :4.000                                             
##  number_of_people_being_liable_to_provide_maintenance_for
##  Min.   :1.000                                           
##  1st Qu.:1.000                                           
##  Median :1.000                                           
##  Mean   :1.155                                           
##  3rd Qu.:1.000                                           
##  Max.   :2.000                                           
##   telephone         foreign_worker      good_loan        
##  Length:1000        Length:1000        Length:1000       
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##                                                          
##                                                          
## 

Ejercicio

Asegurar que el dataset esté en forma tidy, si no lo está haz que esté en formato tidy (puede quedar en prepare/utils) Guarda esto en german-tidy.rds


Algas

¿Quién eres?

Como el dinero no alcanza, tomas otro trabajo rápido para una ONG. Quieren predecir la concentración de algas en ríos de la región. Tomaron datos durante un año.

Cada observación es el efecto de agregar varias muestras de agua recolectadas en el mismo río por un periodo de 3 meses en la misma estación del año.

Datos

Los datos provienen de Coil 1999 Competition Data sobre contaminación de ríos. La explicación de los datos se puede ver aquí

algas_url <- 'https://archive.ics.uci.edu/ml/machine-learning-databases/coil-mld/analysis.data'

algas <- read_csv(algas_url, 
                  col_names = algas_colnames,
                  na = 'XXXXXXX')
## Parsed with column specification:
## cols(
##   season = col_character(),
##   river_size = col_character(),
##   fluid_velocity = col_character(),
##   max_PH = col_double(),
##   min_O2 = col_double(),
##   Cl = col_double(),
##   NO3 = col_character(),
##   NH4 = col_double(),
##   oPO4 = col_double(),
##   PO4 = col_double(),
##   Chla = col_double(),
##   a1 = col_double(),
##   a2 = col_double(),
##   a3 = col_double(),
##   a4 = col_double(),
##   a5 = col_double(),
##   a6 = col_double(),
##   a7 = col_double()
## )
## Warning in rbind(names(probs), probs_f): number of columns of result is not
## a multiple of vector length (arg 1)
## Warning: 17 parsing failures.
## row # A tibble: 5 x 5 col     row   col   expected     actual expected   <int> <chr>      <chr>      <chr> actual 1    20  <NA> 18 columns 17 columns file 2    21  <NA> 18 columns 17 columns row 3    34  <NA> 18 columns 17 columns col 4    35  <NA> 18 columns 17 columns expected 5    36  <NA> 18 columns 17 columns actual # ... with 1 more variables: file <chr>
## ... ................. ... ................................... ........ ................................... ...... ................................... .... ................................... ... ................................... ... ................................... ........ ................................... ...... .......................................
## See problems(...) for more details.

Ejercicio

  • Repite los pasos realizados para german.data con algas
  • Revisa con summary(), reporta alguna anomalía.
summary(algas)
##     season           river_size        fluid_velocity         max_PH     
##  Length:200         Length:200         Length:200         Min.   :5.600  
##  Class :character   Class :character   Class :character   1st Qu.:7.700  
##  Mode  :character   Mode  :character   Mode  :character   Median :8.060  
##                                                           Mean   :8.012  
##                                                           3rd Qu.:8.400  
##                                                           Max.   :9.700  
##                                                           NA's   :1      
##      min_O2             Cl              NO3                 NH4        
##  Min.   : 1.500   Min.   :  0.222   Length:200         Min.   :  5.00  
##  1st Qu.: 7.725   1st Qu.: 10.981   Class :character   1st Qu.: 35.62  
##  Median : 9.800   Median : 32.730   Mode  :character   Median : 99.67  
##  Mean   : 9.118   Mean   : 43.636                      Mean   :154.45  
##  3rd Qu.:10.800   3rd Qu.: 57.824                      3rd Qu.:203.73  
##  Max.   :13.400   Max.   :391.500                      Max.   :931.83  
##  NA's   :2        NA's   :10                           NA's   :2       
##       oPO4             PO4              Chla              a1        
##  Min.   :  1.00   Min.   :  0.90   Min.   :  0.00   Min.   : 0.000  
##  1st Qu.: 16.00   1st Qu.: 19.39   1st Qu.:  2.00   1st Qu.: 1.475  
##  Median : 41.40   Median : 84.50   Median :  5.20   Median : 7.400  
##  Mean   : 83.33   Mean   :111.55   Mean   : 13.54   Mean   :16.863  
##  3rd Qu.:102.25   3rd Qu.:182.16   3rd Qu.: 18.30   3rd Qu.:24.075  
##  Max.   :771.60   Max.   :558.75   Max.   :110.46   Max.   :89.800  
##  NA's   :2        NA's   :2        NA's   :12                       
##        a2               a3               a4               a5       
##  Min.   : 0.000   Min.   : 0.000   Min.   : 0.000   Min.   : 0.00  
##  1st Qu.: 0.000   1st Qu.: 0.000   1st Qu.: 0.000   1st Qu.: 0.00  
##  Median : 2.100   Median : 1.750   Median : 0.000   Median : 1.90  
##  Mean   : 6.934   Mean   : 4.729   Mean   : 1.885   Mean   : 5.63  
##  3rd Qu.: 9.075   3rd Qu.: 6.150   3rd Qu.: 2.225   3rd Qu.: 7.70  
##  Max.   :72.600   Max.   :44.600   Max.   :35.600   Max.   :77.60  
##                                                                    
##        a6               a7        
##  Min.   : 0.000   Min.   : 0.000  
##  1st Qu.: 0.000   1st Qu.: 0.000  
##  Median : 0.000   Median : 0.000  
##  Mean   : 5.199   Mean   : 2.506  
##  3rd Qu.: 6.725   3rd Qu.: 2.400  
##  Max.   :52.500   Max.   :31.600  
##                   NA's   :17

¿Por qué la columna NO3 no es numérica?

problems(algas)

El problema lo podemos observar, por ejemplo, en la observación 20

algas[20,]

Otra cosa interesante a notar, es que hay justo 5 casos de a7 con NAs. Al parecer el error en la columna de NO3 se está “comiendo” a la columna a7.

algas[problems(algas)$row,]

La columna de NO3 está capturada a 5 decimales. Parece lógica la suposición de dividir esa columna a los 5 decimales.

Podemos limpiar esta columna haciendo lo siguiente

problematic_rows <- problems(algas)$row

algas[problematic_rows,] <- algas %>% 
    slice(problematic_rows) %>% 
    unite(col="all", -seq(1:6), sep = "/", remove=TRUE) %>%
    extract(all, into=c("NO3", "NH4", "resto"),
            regex="([0-9]*.[0-9]{5})([0-9]*.[0-9]*)/(.*)/NA", remove=TRUE) %>%
    separate(resto, into=names(algas)[9:18], sep="/", remove=TRUE)    

algas[19:20,]

¿Qué pasó aquí?

algas %>%
  slice(problematic_rows) %>% head()

algas %>% 
  slice(problematic_rows) %>%
  unite(col="all", -seq(1:6), sep="/", remove=T)

algas %>% 
  slice(problematic_rows) %>%
  unite(col="all", -seq(1:6), sep="/", remove=T) %>%
  extract(all, into=c("NO3", "NH4", "resto"),
            regex="([0-9]*.[0-9]{5})([0-9]*.[0-9]*)/(.*)/NA", remove=T)
algas <- readr::type_convert(algas)
## Parsed with column specification:
## cols(
##   season = col_character(),
##   river_size = col_character(),
##   fluid_velocity = col_character(),
##   NO3 = col_double(),
##   NH4 = col_double(),
##   oPO4 = col_double(),
##   PO4 = col_double(),
##   Chla = col_double(),
##   a1 = col_double(),
##   a2 = col_double(),
##   a3 = col_double(),
##   a4 = col_double(),
##   a5 = col_double(),
##   a6 = col_double(),
##   a7 = col_double()
## )
algas
algas <- algas %>%
            mutate_all(funs(algas_clean))

algas

Revisando el resumen estadístico con summary:

summary(algas)
##     season           river_size        fluid_velocity         max_PH     
##  Length:200         Length:200         Length:200         Min.   :5.600  
##  Class :character   Class :character   Class :character   1st Qu.:7.700  
##  Mode  :character   Mode  :character   Mode  :character   Median :8.060  
##                                                           Mean   :8.012  
##                                                           3rd Qu.:8.400  
##                                                           Max.   :9.700  
##                                                           NA's   :1      
##      min_O2             Cl               NO3              NH4          
##  Min.   : 1.500   Min.   :  0.222   Min.   : 0.050   Min.   :    5.00  
##  1st Qu.: 7.725   1st Qu.: 10.981   1st Qu.: 1.296   1st Qu.:   38.33  
##  Median : 9.800   Median : 32.730   Median : 2.675   Median :  103.17  
##  Mean   : 9.118   Mean   : 43.636   Mean   : 3.282   Mean   :  501.30  
##  3rd Qu.:10.800   3rd Qu.: 57.824   3rd Qu.: 4.446   3rd Qu.:  226.95  
##  Max.   :13.400   Max.   :391.500   Max.   :45.650   Max.   :24064.00  
##  NA's   :2        NA's   :10        NA's   :2        NA's   :2         
##       oPO4             PO4              Chla               a1       
##  Min.   :  1.00   Min.   :  1.00   Min.   :  0.200   Min.   : 0.00  
##  1st Qu.: 15.70   1st Qu.: 41.38   1st Qu.:  2.000   1st Qu.: 1.50  
##  Median : 40.15   Median :103.29   Median :  5.475   Median : 6.95  
##  Mean   : 73.59   Mean   :137.88   Mean   : 13.971   Mean   :16.92  
##  3rd Qu.: 99.33   3rd Qu.:213.75   3rd Qu.: 18.308   3rd Qu.:24.80  
##  Max.   :564.60   Max.   :771.60   Max.   :110.456   Max.   :89.80  
##  NA's   :2        NA's   :2        NA's   :12                       
##        a2               a3               a4               a5        
##  Min.   : 0.000   Min.   : 0.000   Min.   : 0.000   Min.   : 0.000  
##  1st Qu.: 0.000   1st Qu.: 0.000   1st Qu.: 0.000   1st Qu.: 0.000  
##  Median : 3.000   Median : 1.550   Median : 0.000   Median : 1.900  
##  Mean   : 7.458   Mean   : 4.309   Mean   : 1.992   Mean   : 5.064  
##  3rd Qu.:11.375   3rd Qu.: 4.925   3rd Qu.: 2.400   3rd Qu.: 7.500  
##  Max.   :72.600   Max.   :42.800   Max.   :44.600   Max.   :44.400  
##                                                                     
##        a6               a7        
##  Min.   : 0.000   Min.   : 0.000  
##  1st Qu.: 0.000   1st Qu.: 0.000  
##  Median : 0.000   Median : 1.000  
##  Mean   : 5.964   Mean   : 2.495  
##  3rd Qu.: 6.925   3rd Qu.: 2.400  
##  Max.   :77.600   Max.   :31.600  
## 

Los tipos de datos:

glimpse(algas)
## Observations: 200
## Variables: 18
## $ season         <chr> "winter", "spring", "autumn", "spring", "autumn...
## $ river_size     <chr> "small_", "small_", "small_", "small_", "small_...
## $ fluid_velocity <chr> "medium", "medium", "medium", "medium", "medium...
## $ max_PH         <dbl> 8.00, 8.35, 8.10, 8.07, 8.06, 8.25, 8.15, 8.05,...
## $ min_O2         <dbl> 9.8, 8.0, 11.4, 4.8, 9.0, 13.1, 10.3, 10.6, 3.4...
## $ Cl             <dbl> 60.800, 57.750, 40.020, 77.364, 55.350, 65.750,...
## $ NO3            <dbl> 6.238, 1.288, 5.330, 2.302, 10.416, 9.248, 1.53...
## $ NH4            <dbl> 578.000, 370.000, 346.667, 98.182, 233.700, 430...
## $ oPO4           <dbl> 105.000, 428.750, 125.667, 61.182, 58.222, 18.2...
## $ PO4            <dbl> 170.000, 558.750, 187.057, 138.700, 97.580, 56....
## $ Chla           <dbl> 50.000, 1.300, 15.600, 1.400, 10.500, 28.400, 3...
## $ a1             <dbl> 0.0, 1.4, 3.3, 3.1, 9.2, 15.1, 2.4, 18.2, 25.4,...
## $ a2             <dbl> 0.0, 7.6, 53.6, 41.0, 2.9, 14.6, 1.2, 1.6, 5.4,...
## $ a3             <dbl> 0.0, 4.8, 1.9, 18.9, 7.5, 1.4, 3.2, 0.0, 2.5, 0...
## $ a4             <dbl> 0.0, 1.9, 0.0, 0.0, 0.0, 0.0, 3.9, 0.0, 0.0, 2....
## $ a5             <dbl> 34.2, 6.7, 0.0, 1.4, 7.5, 22.5, 5.8, 5.5, 0.0, ...
## $ a6             <dbl> 8.3, 0.0, 0.0, 0.0, 4.1, 12.6, 6.8, 8.7, 0.0, 0...
## $ a7             <dbl> 0.0, 2.1, 9.7, 1.4, 1.0, 2.9, 0.0, 0.0, 0.0, 1....

Usando gráficas

  • El resumen estadístico quizá no cuente toda la historia.
  • El siguiente paso es explorar mediante gráficas.
  • Es un proceso iterativo.
Una sola variable
  • ¿Cuál es el pico? ¿Coincide con la media? ¿La mediana? ¿Existe?
  • ¿Cuántos picos?
  • Si es bimodal o multimodal quizá haya varias poblaciones en lugar de una y será mejor modelar por separado.
  • ¿Qué tan normal es? ¿El log-normal?
    • Utiliza una gráfica Q-Q
  • ¿Cuánto varía? ¿Está concentrada en un intervalo o una categoría?
  • ¿Outliers? \(\rightarrow\) Usa gráficas de boxplot.
  • Da preferencia a los density plots, en esta gráfica es más importante la forma que los valores actuales del eje vertical.
  • Si los datos están concentrados en un solo lado de la gráfica (skewed) y es no negativa es bueno representarla en log10.
  • Una grafica de barras no da más información que summary(), aunque algunos las prefieren.
    • Es bueno mostrarla horizontal y ordenada.
Dos variables
  • ¿Existe relación entre dos variables de entrada? ¿entre una entrada y la variable de salida?
  • ¿Qué tan fuerte?
  • ¿Qué tipo de relación?
  • Scatter plot entre dos variables numéricas, calcular la correlación de Pearson en un conjunto sano de los datos, visualizar la curva que mejor representa los datos.
  • Stacked bar charts para dos variables categóricas.
    • Si quieres comparar razones a lo largo de las categorías lo mejor es usar un filled bar chart. En este caso se recomienda agregar un rug para tener una idea de la cantidad de individuos.
    • Si hay múltiples categorías por variable, es mejor usar facets.
  • Para variable categórica y numérica es recomendable usar boxplot (en su versión de violin o jitter).

Tarea 5 Ejercicio. Rmd y html en el git dentro de tu carpeta con el nombre tarea_5_eda. Se entrega máximo el 23 de octubre 2017 23:59:59 CST (-0.5 por cada día de retraso). Enjoy! ╭(◔ ◡ ◔)/

  • Es importante en la etapa de exploración, poder generar varias gráficas de manera automática y simple para analizarlas visualmente y tener una idea de los datos.
  • Crea una función que genere los tipos de gráfica para cada par de variables del data.frame (en realidad es un tibble). Esta función debe de recibir dos parámetros, uno que indique si genera todas las combinaciones de dos variables o recibe una lista de variables en las cuales generar las combinaciones.
  • Guárdala en utils.R.
  • Crea en 03-eda.R en ambas carpetas: algas y german.

Valores faltantes: NAs

  • Los pasos son los siguientes:
    • Identificar los datos faltantes.
    • Examinar las causas de los datos faltantes.
      • Preguntar al domain expert, etc.
    • Borrar los casos (o columnas) que contienen los NAs o reemplazar (imputar) los NAs con valores razonables -puedes ocupar modelos para imputar los NA’s-
  • La teoría la veremos más adelante en el curso.

NOTA: Todas estás recomendaciones aplican igual para outliers

  • Es importante recordar que en R la operación x == NA nunca regresa TRUE, siempre hay que utilizar las funciones is.na(), is.nan() e is.infinite().

  • El método complete.cases identifica los renglones (individuos) del data.frame que no tienen ningún NA en sus columnas (variables).

  • Es posible usar sum y mean con is.na para obtener el total por columna de faltantes y el porcentaje.
    • ¿Por qué?

Aunque más adelante veremos técnicas más poderosas, vale la pena mencionar el ejemplo mostrado en R in action, cap. 15.

La técnica nos permite determinar si los faltantes en una variable están correlacionados con otra.

x <- as.data.frame(abs(is.na(algas))) # df es un data.frame

head(x)
# Extrae las variables que tienen algunas celdas con NAs
y <- x[which(sapply(x, sd) > 0)] 

# Da la correación, un valor alto positivo significa que desaparecen juntas.
cor(y) 
##              max_PH       min_O2          Cl          NO3          NH4
## max_PH  1.000000000 -0.007124524 -0.01626285 -0.007124524 -0.007124524
## min_O2 -0.007124524  1.000000000  0.20751434  0.494949495  0.494949495
## Cl     -0.016262850  0.207514339  1.00000000  0.438085827  0.438085827
## NO3    -0.007124524  0.494949495  0.43808583  1.000000000  1.000000000
## NH4    -0.007124524  0.494949495  0.43808583  1.000000000  1.000000000
## oPO4   -0.007124524  0.494949495  0.43808583  1.000000000  1.000000000
## PO4    -0.007124524 -0.010101010  0.20751434  0.494949495  0.494949495
## Chla   -0.017909570  0.186206796  0.81145218  0.397805428  0.397805428
##                oPO4          PO4        Chla
## max_PH -0.007124524 -0.007124524 -0.01790957
## min_O2  0.494949495 -0.010101010  0.18620680
## Cl      0.438085827  0.207514339  0.81145218
## NO3     1.000000000  0.494949495  0.39780543
## NH4     1.000000000  0.494949495  0.39780543
## oPO4    1.000000000  0.494949495  0.39780543
## PO4     0.494949495  1.000000000  0.18620680
## Chla    0.397805428  0.186206796  1.00000000

Tarea 6. Ejercicio 1

  • Genera un reporte para ambos conjuntos de datos que indique el estado de los valores missing.
  • Muestra la matriz de correlación faltante en una gráfica.
  • ¿Qué puedes interpretar?

Remover observaciones

Las variables con más faltantes son Promedio de Cloruro Cl (10) y Promedio de Clorofila Chla (12).

summary(algas[-grep(colnames(algas),pattern = "^a[1-9]")]) # Nota el uso del grep
##     season           river_size        fluid_velocity         max_PH     
##  Length:200         Length:200         Length:200         Min.   :5.600  
##  Class :character   Class :character   Class :character   1st Qu.:7.700  
##  Mode  :character   Mode  :character   Mode  :character   Median :8.060  
##                                                           Mean   :8.012  
##                                                           3rd Qu.:8.400  
##                                                           Max.   :9.700  
##                                                           NA's   :1      
##      min_O2             Cl               NO3              NH4          
##  Min.   : 1.500   Min.   :  0.222   Min.   : 0.050   Min.   :    5.00  
##  1st Qu.: 7.725   1st Qu.: 10.981   1st Qu.: 1.296   1st Qu.:   38.33  
##  Median : 9.800   Median : 32.730   Median : 2.675   Median :  103.17  
##  Mean   : 9.118   Mean   : 43.636   Mean   : 3.282   Mean   :  501.30  
##  3rd Qu.:10.800   3rd Qu.: 57.824   3rd Qu.: 4.446   3rd Qu.:  226.95  
##  Max.   :13.400   Max.   :391.500   Max.   :45.650   Max.   :24064.00  
##  NA's   :2        NA's   :10        NA's   :2        NA's   :2         
##       oPO4             PO4              Chla        
##  Min.   :  1.00   Min.   :  1.00   Min.   :  0.200  
##  1st Qu.: 15.70   1st Qu.: 41.38   1st Qu.:  2.000  
##  Median : 40.15   Median :103.29   Median :  5.475  
##  Mean   : 73.59   Mean   :137.88   Mean   : 13.971  
##  3rd Qu.: 99.33   3rd Qu.:213.75   3rd Qu.: 18.308  
##  Max.   :564.60   Max.   :771.60   Max.   :110.456  
##  NA's   :2        NA's   :2        NA's   :12

También se puede hacer con dplyr

algas %>%
    select(-starts_with("a")) %>%
    summary()

Antes de removerlas es recomendable verlos, guardarlos y contarlos:

nrow(algas[!complete.cases(algas),])
## [1] 16

Hay 16 observaciones en las cuales tienen NAs

algas_con_NAs <- algas[!complete.cases(algas),]

Siempre es bueno guardarlas si se piensan eliminar del dataset, ¿por qué?

Las observaciones con NAsson las siguientes (usaremos la función print() para explorar)

algas_con_NAs[c('max_PH', 'min_O2', 'Cl', 'NO3', 'NH4', 'oPO4', 'PO4', 'Chla')]  %>%
    print(n = 33)
## # A tibble: 16 x 8
##    max_PH min_O2    Cl   NO3   NH4    oPO4     PO4  Chla
##     <dbl>  <dbl> <dbl> <dbl> <dbl>   <dbl>   <dbl> <dbl>
##  1   6.80   11.1 9.000 0.630    20   4.000      NA  2.70
##  2   8.00     NA 1.450 0.810    10   2.500   3.000  0.30
##  3     NA   12.6 9.000 0.230    10   5.000   6.000  1.10
##  4   6.60   10.8    NA 3.245    10   1.000   6.500    NA
##  5   5.60   11.8    NA 2.220     5   1.000   1.000    NA
##  6   5.70   10.8    NA 2.550    10   1.000   4.000    NA
##  7   6.60    9.5    NA 1.320    20   1.000   6.000    NA
##  8   6.60   10.8    NA 2.640    10   2.000  11.000    NA
##  9   6.60   11.3    NA 4.170    10   1.000   6.000    NA
## 10   6.50   10.4    NA 5.970    10   2.000  14.000    NA
## 11   6.40     NA    NA    NA    NA      NA  14.000    NA
## 12   7.83   11.7 4.083 1.328    18   3.333   6.667    NA
## 13   9.70   10.8 0.222 0.406    10  22.444  10.111    NA
## 14   9.00    5.8    NA 0.900   142 102.000 186.000 68.05
## 15   8.00   10.9 9.055 0.825    40  21.083  56.091    NA
## 16   8.00    7.6    NA    NA    NA      NA      NA    NA

de los cuales, hay dos renglones que tienen más del 50% (6) de las variables independientes nulas.

Aunque remover las observaciones con NAs NO sea la estrategia, quitar las observaciones con muchas columnas vacías, puede ser recomendable.

En los casos en los que no es posible hacer una explorarción visual, se puede utilizar el siguiente código

# ¿Cuántos NAs hay por observación?
apply(algas, 1, function(x) sum(is.na(x)))
##   [1] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0
##  [36] 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 2 2 2 2 2 2 2 6 1 0 0 0 0 0 0 0
##  [71] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## [106] 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## [141] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## [176] 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0

Si queremos ver las observaciones:

algas[apply(algas, 1, function(x) sum(is.na(x))) > 2,]

Lo cual confirma nuestra exploración visual.

Si eliminar las observaciones con NAs va a ser el camino que vamos a tomar, habrá que hacerlo de manera reproducible, lo que sigue es el código de la función `indices_con_NAs’

indices_con_NAs
## function (data, porcentaje = 0.2) 
## {
##     n <- if (porcentaje < 1) {
##         as.integer(porcentaje * ncol(data))
##     }
##     else {
##         stop("Debes de introducir el porcentaje de columnas con NAs.")
##     }
##     indices <- which(apply(data, 1, function(x) sum(is.na(x))) > 
##         n)
##     if (!length(indices)) {
##         warning("No hay observaciones con tantos NAs\\n            (la respuesta de la función es vacía),\\n            no se recomienda indexar el data.frame con esto")
##     }
##     indices
## }
indices_con_NAs(algas, 0.2)
## [1]  62 199
indices_con_NAs(algas, 0.8)
## Warning in indices_con_NAs(algas, 0.8): No hay observaciones con tantos NAs
##             (la respuesta de la función es vacía),
##             no se recomienda indexar el data.frame con esto
## integer(0)
# Si queremos remover las que tengan más del 20% de NAs...
algas <- algas[-indices_con_NAs(algas, 0.2),]
dim(algas)

Renivelar

  • Si la variable es categórica (factor), puedes crear una nueva variable y poner los NAs a un nuevo level, e.g. missing

    • Por ejemplo, suponiendo que hubiese una variable categórica con faltantes en nuestros dataset
dataset$cat_with_NAs_fix <- ifelse(is.na(dataset$cat_with_NAs),
                              "missing",
                              ifelse(dataset$ccat_with_NAs == TRUE,    
                                                # o el valor que sea
                                                "level_1",
                                                "level_2"))
  • Sólo recuerda que es posible que el valor de NA signifique algo.

  • Esto también se puede hacer con variables numéricas, si primero las vuelves categóricas (i.e. binning)

Centralidad

  • Una estrategia es rellenar los valores faltantes con alguna medida de centralidad.
    • Media, mediana, moda, etc.
  • Para variables distribuidas normalmente, esta opción es la mejor.

  • Pero para variables skewed o con outliers esta decisión puede ser desastrosa.

  • Por lo tanto, esta estrategia no se debe de utilizar salvo una exploración previa de las variables.

Tarea 6. Ejercicio 2

  • ¿A qué variables del set de datos de algas les puedes aplicar este procedimiento?
  • ¿Qué puedes decir de german_data?
  • A las variables que no se les puede aplicar, explica por qué no.

  • Esta decisión debe de ser reproducible, agrega a utils.R una función que impute en las variables con NAs el valor central (median si es numérica, moda si es categórica). La función debe de tener la siguiente firma:

imputar_valor_central <- function(data, colnames) {...}

Correlación

Calculando rápidamente la correlación

algas[,-c(1:3)] %>%
    cor(use="complete.obs") %>%
    symnum()
##        m_P m_O Cl NO NH o P Ch a1 a2 a3 a4 a5 a6 a7
## max_PH 1                                           
## min_O2     1                                       
## Cl             1                                   
## NO3               1                                
## NH4               ,  1                             
## oPO4       .   .        1                          
## PO4        .   .        * 1                        
## Chla   .                    1                      
## a1             .        . .    1                   
## a2     .                    .     1                
## a3                                   1             
## a4         .            . .             1          
## a5                                         1       
## a6                .  .                     .  1    
## a7                                               1 
## attr(,"legend")
## [1] 0 ' ' 0.3 '.' 0.6 ',' 0.8 '+' 0.9 '*' 0.95 'B' 1

Observamos que oPO4 y PO4 están altamente relacionadas (> 0.9).

ggplot(data=algas) + 
  aes(x=oPO4, y=PO4) + 
  geom_point(shape=1) + # Usamos una bolita para los puntos
  geom_smooth(method=lm, se=FALSE) +
    theme_hc()

  # Mostramos la linea de la regresión y no mostramos la región de confianza
algas
#algas <- algas[-indices_con_NAs(algas, 0.2),]
modelo <- lm(PO4 ~ oPO4, data=algas)
modelo
## 
## Call:
## lm(formula = PO4 ~ oPO4, data = algas)
## 
## Coefficients:
## (Intercept)         oPO4  
##      42.897        1.293

Entonces la fórmula que relaciona el PO4 con oPO4 es

\[ PO4 = 42.8970464 + 1.2930609*oPO4 \]

Tarea 6. Ejercicio 3

  • Crea una función que sustituya los NAs con el valor dado por la regresión lineal recién calculada (No automatices la regresión lineal) usando la siguiente firma
imputar_valor_lm <- function(var_independiente, modelo) { ... }

Similitud

  • Podemos suponer que si dos observaciones son similares y una de ellas tiene NAs en alguna variable, hay una alta probabilidad de que esa variable tenga un valor similar al valor de esa variable en la otra observación.
    • Obviamente es una suposición…
  • Debemos definir la noción de similar
    • Y esto significa definir un espacio métrico en el espacio que usamos para describir las observaciones.
    • Obviamente, otra gran suposición…
  • Para variables numéricas se puede usar la distancia euclídeana

\[ d(\vec{x}, \vec{y}) = \sqrt{\sum_{i=1}^p(\vec{x}_i - \vec{y}_i)} \]

  • Si son nominales las variables

\[ d(\vec{x}, \vec{y}) = \sqrt{\sum_{i=1}^p \delta_i(\vec{x}_i, \vec{y}_i)} \]

donde \(\delta(\vec{x}, \vec{y})\) es la delta de Kronecker ¿qué? (;-_-)

La dela de Kronecher identifica si dos variables nominales son iguales o no:

\[\delta = \begin{cases} 1 & \text{if } x=0 \\ 0 & \text{if } x>0 \end{cases}\]

Ejemplos:

  • \(\delta_{100}^{100}=1\)
  • \(\delta_4^{134}=0\)
  • \(\delta_{-1}^{i\pi}=1\)
  • \(\delta_{7,2^3-1,\frac{21}{3}}=1\)
  • \(\delta_{-1}^{sin(2017 \sqrt[6]{2})}=0\)

  • Una vez definida la similitud, debemos de definir el valor que imputar al NA.
  • Una opción es utilizar una medida de centralidad de los \(k\) observaciones más cercanas.
  • El Promedio con peso de los valores de los vecinos, es otra opción. El peso se puede determinar de varias maneras, pero usar como kernel una función gaussiana.

\[ peso(d) = e^{-d} \]

donde \(d\) es la distancia de una observación a la que estamos considerando.

  • Es importante estandarizar los valores numéricos antes de calcular las distancias.

\[ \vec{x}_{normalizado} = \frac{\vec{x}_i - \bar{x}}{\sigma_{x}} \]

¿Por qué?

Tarea 6. Ejercicio 4

  • Implementa una función que impute por similitud con la firma
imputar_por_similitud <- function(data, num_vecinos) { ... }
  • Aplícalo a algas y german.
  • ¿Son muy diferentes las estadísticas ignorando los NAs comparadas con este método?

Transformación de datos

Paréntesis cultural, en minería de datos:

  • Normalizar \[x_{nuevo}=\frac{x-x_{min}}{x_{max}-x_{min}}\]
  • Estandarizar \[x_{nuevo}=\frac{x-\mu}{\sigma}\]

Normalizar/estandarizar es útil cuando las cantidades absolutas son menos importantes que las relativas.

  • Normalizar y reescalar
    • Usar la desviación estándar como unidad de medida.
    • Tiene mucho sentido si la distribución es simétrica.
    • Si no lo es, es posible que sea lognormally distributed (como el ingreso monetario o los gastos), una transformación log10() lo hará útil.
  • Es una buena idea usar log si el rango de tus datos cubre varios ordenes de magnitud.
    • Regularmente, estas variables vienen de procesos multiplicativos en lugar de aditivos.
  • Si el rango incluye cantidades negativas, usa (crea) una función signedLog10
signedLog10 <- function(x) {
  ifelse(abs(x) <= 1.0, sign(x)*log10(abs(x)))
}

Tarea 6, Ejercicio 5

  • Este es un buen momento para dejar de duplicar código y concentrar todas las funciones de utils.R que se puedan reutilizar en un archivo toolset.R. Ajusta tus demás archivos de acuerdo a este cambio -si aplica-
  • Crea un R notebook para cada dataset utilizando los archivos reproducibles y el archivo toolset.R. Incluye en estos notebook la estructura de los datos, GEDA transformaciones de los datos y observaciones pertinentes (como outliers, estructura de los datos faltantes). Explica los métodos de imputación que usaste (si fué necesario) y porqué los usaste.

EDA \(\to\) modelado

  • En general la exploración de datos se divide en tres pasos:
    • Verificar la distribución de las variables individuales
      • Identificando outliers, valores faltantes \(\to\) transformación, eliminación del dataset, etc.
    • Verificar la relación entre las variables dependientes y los predictores
      • Se podrá usar en feature selection
    • Relación entre los predictores
      • Eliminación de variables redundantes

Titanic

Datos relacionales y la cuestión de la Semántica

Titanic

Titanic

titanic_path <- '../data/Titanic/titanic.ods'

ds_names <- ods_sheets(titanic_path)
ds_names
##  [1] "1er Clase"       "2da Clase"       "3era Clase"     
##  [4] "Deck"            "Engine"          "Victualling"    
##  [7] "Restaurant"      "Postal Clerk"    "Guarantee Group"
## [10] "Ship's Orchesta" "Discharged crew"

Arreglemos los nombres de los data sets

clean_sheet_name <- function(sheet_name) {
    str_replace_all(str_replace_all(string=sheet_name, pattern=" ", replace="_"), pattern="'", replace="") %>% 
    str_to_lower()
}

sapply(ds_names, clean_sheet_name)
##         1er Clase         2da Clase        3era Clase              Deck 
##       "1er_clase"       "2da_clase"      "3era_clase"            "deck" 
##            Engine       Victualling        Restaurant      Postal Clerk 
##          "engine"     "victualling"      "restaurant"    "postal_clerk" 
##   Guarantee Group   Ship's Orchesta   Discharged crew 
## "guarantee_group"  "ships_orchesta" "discharged_crew"

Ahora obtenemos los datasets y los guardamos

save_sheet <- function(sheet_name) {
    file_name <-  paste0("../data/Titanic/", clean_sheet_name(sheet_name), ".rds")
    saveRDS(object = read_ods(titanic_path, sheet = sheet_name), file = file_name)
}


lapply(ods_sheets(titanic_path), save_sheet)
## [[1]]
## NULL
## 
## [[2]]
## NULL
## 
## [[3]]
## NULL
## 
## [[4]]
## NULL
## 
## [[5]]
## NULL
## 
## [[6]]
## NULL
## 
## [[7]]
## NULL
## 
## [[8]]
## NULL
## 
## [[9]]
## NULL
## 
## [[10]]
## NULL
## 
## [[11]]
## NULL

Cargar los datos:

rm(list=ls())

rds_files <- dir("../data/Titanic/", pattern = "*.rds", full.names = TRUE)

#lapply te devolverá las cosas en un lista... una lista de dataframes :)
ds <- lapply(rds_files, read_rds)
class(ds)
## [1] "list"
class(ds[[1]])
## [1] "data.frame"
#cuantos dataframes contiene esta lista? 
length(ds)
## [1] 11
#basename elimina todo el path del nombre excepto la última parte (se quedará con la extensión del archivo!), ?basename
names(ds) <- lapply(rds_files, basename)
names(ds)
##  [1] "1er_clase.rds"       "2da_clase.rds"       "3era_clase.rds"     
##  [4] "deck.rds"            "discharged_crew.rds" "engine.rds"         
##  [7] "guarantee_group.rds" "postal_clerk.rds"    "restaurant.rds"     
## [10] "ships_orchesta.rds"  "victualling.rds"
  1. En algunos data sets se agregaron columnas de más, remuévelas
#veamos qué nombres tiene cada dataframe
lapply(ds, names)
## $`1er_clase.rds`
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"       ""           " "         
## 
## $`2da_clase.rds`
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"       ""           " "         
## 
## $`3era_clase.rds`
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"      
## 
## $deck.rds
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"      
## 
## $discharged_crew.rds
## [1] "" ""
## 
## $engine.rds
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"      
## 
## $guarantee_group.rds
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"      
## 
## $postal_clerk.rds
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"       ""           ""          
## 
## $restaurant.rds
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"       ""           " "         
## 
## $ships_orchesta.rds
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"      
## 
## $victualling.rds
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"       ""           " "
#si quisieramos obtener los conjuntos de nombres únicos
lapply(ds, names) %>% unique()
## [[1]]
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"       ""           " "         
## 
## [[2]]
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"      
## 
## [[3]]
## [1] "" ""
## 
## [[4]]
##  [1] "Name"       "Age"        "Class/Dept" "Ticket"     "Fare"      
##  [6] "Group"      "Ship"       "Joined"     "Job"        "Boat"      
## [11] "Body"       ""           ""

Vemos que hay 4 tipos de listas de nombres diferentes:

  • Las que tienen la últimas columna con un espacio de nombre y la penúltima sin nombre
  • Las que no tienen columnas demás
  • Las que de plano no tienen nombres
  • Las que tienen las últimas dos columnas sin nombre -sutil diferencia con el primer caso-

Averigüemos más:

lapply(ds, head)
## $`1er_clase.rds`
##                                  Name Age Class/Dept Ticket       Fare
## 1        ALLEN, Miss Elisabeth Walton  29  1st Class  24160 £211 6s 9d
## 2 ALLISON, Mr Hudson Joshua Creighton  30  1st Class 113781   £151 16s
## 3           ALLISON, Mrs Bessie Waldo  25  1st Class 113781   £151 16s
## 4         ALLISON, Miss Helen Loraine   2  1st Class 113781   £151 16s
## 5       ALLISON, Master Hudson Trevor 11m  1st Class 113781   £151 16s
## 6                  ANDERSON, Mr Harry  47  1st Class  19952    £26 11s
##   Group Ship      Joined         Job Boat Body          
## 1  <NA> <NA> Southampton        <NA>    2   NA <NA> <NA>
## 2  <NA> <NA> Southampton Businessman <NA>  135 <NA> <NA>
## 3  <NA> <NA> Southampton        <NA> <NA>   NA <NA> <NA>
## 4  <NA> <NA> Southampton        <NA> <NA>   NA <NA> <NA>
## 5  <NA> <NA> Southampton        <NA>   11   NA <NA> <NA>
## 6  <NA> <NA> Southampton Stockbroker    3   NA <NA> <NA>
## 
## $`2da_clase.rds`
##                            Name Age Class/Dept Ticket    Fare   Group Ship
## 1            ABELSON, Mr Samuel  30  2nd Class   3381     £24    <NA> <NA>
## 2                  ABELSON, Mrs  28  2nd Class   3381     £24    <NA> <NA>
## 3 ALDWORTH, Mr Charles Augustus  30  2nd Class 248744     £13 Servant <NA>
## 4       ANDREW, Mr Edgar Samuel  17  2nd Class 231945 £11 10s    <NA> <NA>
## 5       ANDREW, Mr Frank Thomas  25  2nd Class  34050 £10 10s    <NA> <NA>
## 6          ANGLE, Mr William A.  32  2nd Class 226875     £26    <NA> <NA>
##        Joined       Job Boat Body          
## 1   Cherbourg      <NA> <NA>   NA <NA> <NA>
## 2   Cherbourg      <NA>   10   NA <NA> <NA>
## 3 Southampton Chauffeur <NA>   NA <NA> <NA>
## 4 Southampton      <NA> <NA>   NA <NA> <NA>
## 5 Southampton     Miner <NA>   NA <NA> <NA>
## 6 Southampton      <NA> <NA>   NA <NA> <NA>
## 
## $`3era_clase.rds`
##                            Name Age Class/Dept Ticket   Fare Group Ship
## 1            ABBING, Mr Anthony  42  3rd Class   5547 £7 11s  <NA> <NA>
## 2 ABBOTT, Mrs Rhoda Mary 'Rosa'  39  3rd Class CA2673 £20 5s  <NA> <NA>
## 3    ABBOTT, Mr Rossmore Edward  16  3rd Class CA2673 £20 5s  <NA> <NA>
## 4      ABBOTT, Mr Eugene Joseph  14  3rd Class CA2673 £20 5s  <NA> <NA>
## 5    ABELSETH, Miss Karen Marie  16  3rd Class 348125 £7 13s  <NA> <NA>
## 6  ABELSETH, Mr Olaus Jørgensen  25  3rd Class 348122 £7 13s  <NA> <NA>
##        Joined        Job Boat Body
## 1 Southampton Blacksmith <NA>   NA
## 2 Southampton       <NA>    A   NA
## 3 Southampton   Jeweller <NA>  190
## 4 Southampton    Scholar <NA>   NA
## 5 Southampton       <NA>   16   NA
## 6 Southampton     Farmer    A   NA
## 
## $deck.rds
##                        Name Age Class/Dept Ticket Fare Group Ship
## 1        ANDERSON, Mr James  40       Deck   <NA> <NA>  <NA> <NA>
## 2  ARCHER, Mr Ernest Edward  36       Deck   <NA> <NA>  <NA> <NA>
## 3   BAILEY, Mr Henry Joseph  43       Deck   <NA> <NA>  <NA> <NA>
## 4 BOXHALL, Mr Joseph Groves  28       Deck   <NA> <NA>  <NA> <NA>
## 5            BRADLEY, Mr T.  29       Deck   <NA> <NA>  <NA> <NA>
## 6        BRICE, Mr Walter T  42       Deck   <NA> <NA>  <NA> <NA>
##        Joined            Job Boat Body
## 1 Southampton    Able Seaman    3   NA
## 2 Southampton    Able Seaman   16   NA
## 3 Southampton Master-at-arms   16   NA
## 4     Belfast   4th. Officer    2   NA
## 5 Southampton    Able Seaman <NA>   NA
## 6 Southampton    Able Seaman   11   NA
## 
## $discharged_crew.rds
##                                                          
## 1                  <NA>                              <NA>
## 2          Blake, Mr C.        [Trimmer - Failed to Join]
## 3 Bowman, Mr J. (?F.T.) [Assistant Cook - Failed to Join]
## 4         Brewer, Mr B.              [Trimmer - Deserted]
## 5        Burrows, Mr W.       [Fireman - Left by Consent]
## 6         Carter, Mr F.        [Trimmer - Failed to join]
## 
## $engine.rds
##                         Name Age Class/Dept Ticket Fare Group Ship
## 1         ABRAMS, Mr William  33     Engine   <NA> <NA>  <NA> <NA>
## 2               ADAMS, Mr R.  26     Engine   <NA> <NA>  <NA> <NA>
## 3            ALLEN, Mr Henry  32     Engine   <NA> <NA>  <NA> <NA>
## 4 ALLEN, Mr Ernest Frederick  24     Engine   <NA> <NA>  <NA> <NA>
## 5   ALLSOP, Mr Alfred Samuel  34     Engine   <NA> <NA>  <NA> <NA>
## 6     AVERY, Mr James Albert  22     Engine   <NA> <NA>  <NA> <NA>
##        Joined              Job Boat Body
## 1 Southampton Fireman / Stoker <NA>   NA
## 2 Southampton Fireman / Stoker <NA>   NA
## 3 Southampton Fireman / Stoker <NA>  145
## 4 Southampton          Trimmer    B   NA
## 5     Belfast      Electrician <NA>   NA
## 6 Southampton          Trimmer   15   NA
## 
## $guarantee_group.rds
##                                   Name Age Class/Dept Ticket Fare
## 1                   ANDREWS, Mr Thomas  39  1st Class 112050 <NA>
## 2           CAMPBELL, Mr William Henry  21  2nd Class 239853 <NA>
## 3 CHISHOLM, Mr Roderick Robert Crispin  40  1st Class 112051 <NA>
## 4        CUNNINGHAM, Mr Alfred Fleming  21  2nd Class 239853 <NA>
## 5               FROST, Mr Anthony Wood  37  2nd Class 239854 <NA>
## 6                 KNIGHT, Mr Robert J.  39  2nd Class 239855 <NA>
##                 Group Ship  Joined         Job Boat Body
## 1 H&W Guarantee Group <NA> Belfast Shipbuilder <NA> <NA>
## 2 H&W Guarantee Group <NA> Belfast        <NA> <NA> <NA>
## 3 H&W Guarantee Group <NA> Belfast Draughtsman <NA> <NA>
## 4 H&W Guarantee Group <NA> Belfast      Fitter <NA> <NA>
## 5 H&W Guarantee Group <NA> Belfast      Fitter <NA> <NA>
## 6 H&W Guarantee Group <NA> Belfast      Fitter <NA> <NA>
## 
## $postal_clerk.rds
##                           Name Age  Class/Dept Ticket Fare        Group
## 1      GWYNN, Mr William Logan  37 Victualling   <NA> <NA> Postal Clerk
## 2         MARCH, Mr John Starr  50 Victualling   <NA> <NA> Postal Clerk
## 3  SMITH, Mr John Richard Jago  35 Victualling   <NA> <NA> Postal Clerk
## 4 WILLIAMSON, Mr James Bertram  35 Victualling   <NA> <NA> Postal Clerk
## 5        WOODY, Mr Oscar Scott  44 Victualling   <NA> <NA> Postal Clerk
## 6                         <NA>  NA        <NA>   <NA> <NA>         <NA>
##   Ship      Joined          Job Boat Body          
## 1 <NA> Southampton Postal Clerk <NA>   NA <NA> <NA>
## 2 <NA> Southampton Postal Clerk <NA>  225 <NA> <NA>
## 3 <NA> Southampton Postal Clerk <NA>   NA <NA> <NA>
## 4 <NA> Southampton Postal Clerk <NA>   NA <NA> <NA>
## 5 <NA> Southampton Postal Clerk <NA>  167 <NA> <NA>
## 6 <NA>        <NA>         <NA> <NA>   NA <NA> <NA>
## 
## $restaurant.rds
##                             Name Age Class/Dept Ticket Fare Group Ship
## 1 ALLARIA, Sig. Battista Antonio  22 A la Carte   <NA> <NA>  <NA> <NA>
## 2          ASPESLAGH, Mr Georges  26 A la Carte   <NA> <NA>  <NA> <NA>
## 3                BANFI, Sig. Ugo  24 A la Carte   <NA> <NA>  <NA> <NA>
## 4        BASILICO, Sig. Giovanni  27 A la Carte   <NA> <NA>  <NA> <NA>
## 5            BAZZI, Sig. Narciso  33 A la Carte   <NA> <NA>  <NA> <NA>
## 6        BERNARDI, Sig. Battista  22 A la Carte   <NA> <NA>  <NA> <NA>
##        Joined                Job Boat Body          
## 1 Southampton   Assistant Waiter <NA>  221 <NA> <NA>
## 2 Southampton Assistant Plateman <NA>   NA <NA> <NA>
## 3 Southampton             Waiter <NA>   NA <NA> <NA>
## 4 Southampton             Waiter <NA>   NA <NA> <NA>
## 5 Southampton             Waiter <NA>   NA <NA> <NA>
## 6 Southampton   Assistant Waiter <NA>  215 <NA> <NA>
## 
## $ships_orchesta.rds
##                                Name Age Class/Dept Ticket Fare    Group
## 1    BRAILEY, Mr W. Theodore Ronald  24  2nd Class 250654 <NA> Musician
## 2           BRICOUX, Mr Roger Marie  20  2nd Class 250654 <NA> Musician
## 3 CLARKE, Mr John Frederick Preston  30  2nd Class 250654 <NA> Musician
## 4         HARTLEY, Mr Wallace Henry  33  2nd Class 250654 <NA> Musician
## 5                 HUME, Mr John Law  21  2nd Class 250654 <NA> Musician
## 6       KRINS, Mr Georges Alexandre  23  2nd Class 250654 <NA> Musician
##   Ship      Joined      Job Boat Body
## 1 <NA> Southampton Musician <NA>   NA
## 2 <NA> Southampton Musician <NA>   NA
## 3 <NA> Southampton Musician <NA>  202
## 4 <NA> Southampton Musician <NA>  224
## 5 <NA> Southampton Musician <NA>  193
## 6 <NA> Southampton Musician <NA>   NA
## 
## $victualling.rds
##                         Name Age  Class/Dept Ticket Fare Group Ship
## 1     ABBOTT, Mr Ernest Owen  21 Victualling   <NA> <NA>  <NA> <NA>
## 2    AHIER, Mr Percy Snowden  20 Victualling   <NA> <NA>  <NA> <NA>
## 3         AKERMAN, Mr Albert  28 Victualling   <NA> <NA>  <NA> <NA>
## 4 AKERMAN, Mr Joseph Francis  35 Victualling   <NA> <NA>  <NA> <NA>
## 5   ALLAN, Mr Robert Spencer  36 Victualling   <NA> <NA>  <NA> <NA>
## 6           ALLEN, Mr George  26 Victualling   <NA> <NA>  <NA> <NA>
##        Joined                         Job Boat Body          
## 1 Southampton       Lounge Pantry Steward <NA>   NA <NA> <NA>
## 2 Southampton              Saloon Steward <NA>   NA <NA> <NA>
## 3 Southampton                     Steward <NA>   NA <NA> <NA>
## 4 Southampton Assistant Pantryman Steward <NA>  205 <NA> <NA>
## 5 Southampton            Bed Room Steward <NA>   NA <NA> <NA>
## 6 Southampton                    Scullion <NA>   NA <NA> <NA>

El data frame discharged_crew.rds tiene dos columnas que no tienen nombre y parecen contener el nombre del empleado, el oficio y la razón de su salida como empleado del barcof. Quitemos este data frame de nuestro set de datos

ds <- ds[-which(lapply(lapply(ds, names), length) == 2)]
  1. Juntemos los data frames en uno solo y quitemos las columnas que están demás

Obtengamos el número mínimo de nombres como base (los demás tienen columnas vacías)

num_cols <- lapply((lapply(ds, names)), length) %>% unlist() %>% min()
num_cols 
## [1] 11

Pero… primero revisemos que los tipos de datos son los mismos -no vaya a ser-

lapply(ds, str)
## 'data.frame':    324 obs. of  13 variables:
##  $ Name      : chr  "ALLEN, Miss Elisabeth Walton" "ALLISON, Mr Hudson Joshua Creighton" "ALLISON, Mrs Bessie Waldo" "ALLISON, Miss Helen Loraine" ...
##  $ Age       : chr  "29" "30" "25" "2" ...
##  $ Class/Dept: chr  "1st Class" "1st Class" "1st Class" "1st Class" ...
##  $ Ticket    : chr  "24160" "113781" "113781" "113781" ...
##  $ Fare      : chr  "£211 6s 9d" "£151 16s" "£151 16s" "£151 16s" ...
##  $ Group     : chr  NA NA NA NA ...
##  $ Ship      : chr  NA NA NA NA ...
##  $ Joined    : chr  "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr  NA "Businessman" NA NA ...
##  $ Boat      : chr  "2" NA NA NA ...
##  $ Body      : int  NA 135 NA NA NA NA NA NA NA 22 ...
##  $           : chr  NA NA NA NA ...
##  $           : chr  NA NA NA NA ...
## 'data.frame':    285 obs. of  13 variables:
##  $ Name      : chr  "ABELSON, Mr Samuel" "ABELSON, Mrs" "ALDWORTH, Mr Charles Augustus" "ANDREW, Mr Edgar Samuel" ...
##  $ Age       : chr  "30" "28" "30" "17" ...
##  $ Class/Dept: chr  "2nd Class" "2nd Class" "2nd Class" "2nd Class" ...
##  $ Ticket    : chr  "3381" "3381" "248744" "231945" ...
##  $ Fare      : chr  "£24" "£24" "£13" "£11 10s" ...
##  $ Group     : chr  NA NA "Servant" NA ...
##  $ Ship      : chr  NA NA NA NA ...
##  $ Joined    : chr  "Cherbourg" "Cherbourg" "Southampton" "Southampton" ...
##  $ Job       : chr  NA NA "Chauffeur" NA ...
##  $ Boat      : chr  NA "10" NA NA ...
##  $ Body      : int  NA NA NA NA NA NA NA NA NA NA ...
##  $           : chr  NA NA NA NA ...
##  $           : chr  NA NA NA NA ...
## 'data.frame':    708 obs. of  11 variables:
##  $ Name      : chr  "ABBING, Mr Anthony" "ABBOTT, Mrs Rhoda Mary 'Rosa'" "ABBOTT, Mr Rossmore Edward" "ABBOTT, Mr Eugene Joseph" ...
##  $ Age       : chr  "42" "39" "16" "14" ...
##  $ Class/Dept: chr  "3rd Class" "3rd Class" "3rd Class" "3rd Class" ...
##  $ Ticket    : chr  "5547" "CA2673" "CA2673" "CA2673" ...
##  $ Fare      : chr  "£7 11s" "£20 5s" "£20 5s" "£20 5s" ...
##  $ Group     : chr  NA NA NA NA ...
##  $ Ship      : chr  NA NA NA NA ...
##  $ Joined    : chr  "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr  "Blacksmith" NA "Jeweller" "Scholar" ...
##  $ Boat      : chr  NA "A" NA NA ...
##  $ Body      : int  NA NA 190 NA NA NA NA NA 72 103 ...
## 'data.frame':    66 obs. of  11 variables:
##  $ Name      : chr  "ANDERSON, Mr James" "ARCHER, Mr Ernest Edward" "BAILEY, Mr Henry Joseph" "BOXHALL, Mr Joseph Groves" ...
##  $ Age       : int  40 36 43 28 29 42 42 27 34 31 ...
##  $ Class/Dept: chr  "Deck" "Deck" "Deck" "Deck" ...
##  $ Ticket    : chr  NA NA NA NA ...
##  $ Fare      : chr  NA NA NA NA ...
##  $ Group     : chr  NA NA NA NA ...
##  $ Ship      : chr  NA NA NA NA ...
##  $ Joined    : chr  "Southampton" "Southampton" "Southampton" "Belfast" ...
##  $ Job       : chr  "Able Seaman" "Able Seaman" "Master-at-arms" "4th. Officer" ...
##  $ Boat      : chr  "3" "16" "16" "2" ...
##  $ Body      : int  NA NA NA NA NA NA NA NA NA NA ...
## 'data.frame':    325 obs. of  11 variables:
##  $ Name      : chr  "ABRAMS, Mr William" "ADAMS, Mr R." "ALLEN, Mr Henry" "ALLEN, Mr Ernest Frederick" ...
##  $ Age       : int  33 26 32 24 34 22 46 24 32 30 ...
##  $ Class/Dept: chr  "Engine" "Engine" "Engine" "Engine" ...
##  $ Ticket    : chr  NA NA NA NA ...
##  $ Fare      : chr  NA NA NA NA ...
##  $ Group     : chr  NA NA NA NA ...
##  $ Ship      : chr  NA NA NA NA ...
##  $ Joined    : chr  "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr  "Fireman / Stoker" "Fireman / Stoker" "Fireman / Stoker" "Trimmer" ...
##  $ Boat      : chr  NA NA NA "B" ...
##  $ Body      : int  NA NA 145 NA NA NA NA NA NA NA ...
## 'data.frame':    9 obs. of  11 variables:
##  $ Name      : chr  "ANDREWS, Mr Thomas" "CAMPBELL, Mr William Henry" "CHISHOLM, Mr Roderick Robert Crispin" "CUNNINGHAM, Mr Alfred Fleming" ...
##  $ Age       : int  39 21 40 21 37 39 18 29 15
##  $ Class/Dept: chr  "1st Class" "2nd Class" "1st Class" "2nd Class" ...
##  $ Ticket    : int  112050 239853 112051 239853 239854 239855 239853 112052 239856
##  $ Fare      : chr  NA NA NA NA ...
##  $ Group     : chr  "H&W Guarantee Group" "H&W Guarantee Group" "H&W Guarantee Group" "H&W Guarantee Group" ...
##  $ Ship      : chr  NA NA NA NA ...
##  $ Joined    : chr  "Belfast" "Belfast" "Belfast" "Belfast" ...
##  $ Job       : chr  "Shipbuilder" NA "Draughtsman" "Fitter" ...
##  $ Boat      : chr  NA NA NA NA ...
##  $ Body      : chr  NA NA NA NA ...
## 'data.frame':    15 obs. of  13 variables:
##  $ Name      : chr  "GWYNN, Mr William Logan" "MARCH, Mr John Starr" "SMITH, Mr John Richard Jago" "WILLIAMSON, Mr James Bertram" ...
##  $ Age       : int  37 50 35 35 44 NA NA NA NA NA ...
##  $ Class/Dept: chr  "Victualling" "Victualling" "Victualling" "Victualling" ...
##  $ Ticket    : chr  NA NA NA NA ...
##  $ Fare      : chr  NA NA NA NA ...
##  $ Group     : chr  "Postal Clerk" "Postal Clerk" "Postal Clerk" "Postal Clerk" ...
##  $ Ship      : chr  NA NA NA NA ...
##  $ Joined    : chr  "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr  "Postal Clerk" "Postal Clerk" "Postal Clerk" "Postal Clerk" ...
##  $ Boat      : chr  NA NA NA NA ...
##  $ Body      : int  NA 225 NA NA 167 NA NA NA NA NA ...
##  $           : chr  NA NA NA NA ...
##  $           : chr  NA NA NA NA ...
## 'data.frame':    69 obs. of  13 variables:
##  $ Name      : chr  "ALLARIA, Sig. Battista Antonio" "ASPESLAGH, Mr Georges" "BANFI, Sig. Ugo" "BASILICO, Sig. Giovanni" ...
##  $ Age       : int  22 26 24 27 33 22 26 28 26 NA ...
##  $ Class/Dept: chr  "A la Carte" "A la Carte" "A la Carte" "A la Carte" ...
##  $ Ticket    : chr  NA NA NA NA ...
##  $ Fare      : chr  NA NA NA NA ...
##  $ Group     : chr  NA NA NA NA ...
##  $ Ship      : chr  NA NA NA NA ...
##  $ Joined    : chr  "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr  "Assistant Waiter" "Assistant Plateman" "Waiter" "Waiter" ...
##  $ Boat      : chr  NA NA NA NA ...
##  $ Body      : int  221 NA NA NA NA 215 NA NA NA NA ...
##  $           : chr  NA NA NA NA ...
##  $           : chr  NA NA NA NA ...
## 'data.frame':    8 obs. of  11 variables:
##  $ Name      : chr  "BRAILEY, Mr W. Theodore Ronald" "BRICOUX, Mr Roger Marie" "CLARKE, Mr John Frederick Preston" "HARTLEY, Mr Wallace Henry" ...
##  $ Age       : int  24 20 30 33 21 23 32 32
##  $ Class/Dept: chr  "2nd Class" "2nd Class" "2nd Class" "2nd Class" ...
##  $ Ticket    : int  250654 250654 250654 250654 250654 250654 250654 250654
##  $ Fare      : chr  NA NA NA NA ...
##  $ Group     : chr  "Musician" "Musician" "Musician" "Musician" ...
##  $ Ship      : chr  NA NA NA NA ...
##  $ Joined    : chr  "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr  "Musician" "Musician" "Musician" "Musician" ...
##  $ Boat      : chr  NA NA NA NA ...
##  $ Body      : int  NA NA 202 224 193 NA NA NA
## 'data.frame':    431 obs. of  13 variables:
##  $ Name      : chr  "ABBOTT, Mr Ernest Owen" "AHIER, Mr Percy Snowden" "AKERMAN, Mr Albert" "AKERMAN, Mr Joseph Francis" ...
##  $ Age       : int  21 20 28 35 36 26 17 41 48 19 ...
##  $ Class/Dept: chr  "Victualling" "Victualling" "Victualling" "Victualling" ...
##  $ Ticket    : chr  NA NA NA NA ...
##  $ Fare      : chr  NA NA NA NA ...
##  $ Group     : chr  NA NA NA NA ...
##  $ Ship      : chr  NA NA NA NA ...
##  $ Joined    : chr  "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr  "Lounge Pantry Steward" "Saloon Steward" "Steward" "Assistant Pantryman Steward" ...
##  $ Boat      : chr  NA NA NA NA ...
##  $ Body      : int  NA NA NA 205 NA NA NA NA 146 NA ...
##  $           : chr  NA NA NA NA ...
##  $           : chr  NA NA NA NA ...
## $`1er_clase.rds`
## NULL
## 
## $`2da_clase.rds`
## NULL
## 
## $`3era_clase.rds`
## NULL
## 
## $deck.rds
## NULL
## 
## $engine.rds
## NULL
## 
## $guarantee_group.rds
## NULL
## 
## $postal_clerk.rds
## NULL
## 
## $restaurant.rds
## NULL
## 
## $ships_orchesta.rds
## NULL
## 
## $victualling.rds
## NULL

(╯°□°)╯︵ ┻━┻ Era demasiado bello para ser real! Age y Body en algunos dataframes son int en algunos son chr… no los podemos juntar si son de diferentes tipos cambiemos a chr todas las columnas por facilidad

ds <- lapply(ds, function(x) lapply(x, as.character))
#verifiquemos 
lapply(ds, str)
## List of 13
##  $ Name      : chr [1:324] "ALLEN, Miss Elisabeth Walton" "ALLISON, Mr Hudson Joshua Creighton" "ALLISON, Mrs Bessie Waldo" "ALLISON, Miss Helen Loraine" ...
##  $ Age       : chr [1:324] "29" "30" "25" "2" ...
##  $ Class/Dept: chr [1:324] "1st Class" "1st Class" "1st Class" "1st Class" ...
##  $ Ticket    : chr [1:324] "24160" "113781" "113781" "113781" ...
##  $ Fare      : chr [1:324] "£211 6s 9d" "£151 16s" "£151 16s" "£151 16s" ...
##  $ Group     : chr [1:324] NA NA NA NA ...
##  $ Ship      : chr [1:324] NA NA NA NA ...
##  $ Joined    : chr [1:324] "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr [1:324] NA "Businessman" NA NA ...
##  $ Boat      : chr [1:324] "2" NA NA NA ...
##  $ Body      : chr [1:324] NA "135" NA NA ...
##  $           : chr [1:324] NA NA NA NA ...
##  $           : chr [1:324] NA NA NA NA ...
## List of 13
##  $ Name      : chr [1:285] "ABELSON, Mr Samuel" "ABELSON, Mrs" "ALDWORTH, Mr Charles Augustus" "ANDREW, Mr Edgar Samuel" ...
##  $ Age       : chr [1:285] "30" "28" "30" "17" ...
##  $ Class/Dept: chr [1:285] "2nd Class" "2nd Class" "2nd Class" "2nd Class" ...
##  $ Ticket    : chr [1:285] "3381" "3381" "248744" "231945" ...
##  $ Fare      : chr [1:285] "£24" "£24" "£13" "£11 10s" ...
##  $ Group     : chr [1:285] NA NA "Servant" NA ...
##  $ Ship      : chr [1:285] NA NA NA NA ...
##  $ Joined    : chr [1:285] "Cherbourg" "Cherbourg" "Southampton" "Southampton" ...
##  $ Job       : chr [1:285] NA NA "Chauffeur" NA ...
##  $ Boat      : chr [1:285] NA "10" NA NA ...
##  $ Body      : chr [1:285] NA NA NA NA ...
##  $           : chr [1:285] NA NA NA NA ...
##  $           : chr [1:285] NA NA NA NA ...
## List of 11
##  $ Name      : chr [1:708] "ABBING, Mr Anthony" "ABBOTT, Mrs Rhoda Mary 'Rosa'" "ABBOTT, Mr Rossmore Edward" "ABBOTT, Mr Eugene Joseph" ...
##  $ Age       : chr [1:708] "42" "39" "16" "14" ...
##  $ Class/Dept: chr [1:708] "3rd Class" "3rd Class" "3rd Class" "3rd Class" ...
##  $ Ticket    : chr [1:708] "5547" "CA2673" "CA2673" "CA2673" ...
##  $ Fare      : chr [1:708] "£7 11s" "£20 5s" "£20 5s" "£20 5s" ...
##  $ Group     : chr [1:708] NA NA NA NA ...
##  $ Ship      : chr [1:708] NA NA NA NA ...
##  $ Joined    : chr [1:708] "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr [1:708] "Blacksmith" NA "Jeweller" "Scholar" ...
##  $ Boat      : chr [1:708] NA "A" NA NA ...
##  $ Body      : chr [1:708] NA NA "190" NA ...
## List of 11
##  $ Name      : chr [1:66] "ANDERSON, Mr James" "ARCHER, Mr Ernest Edward" "BAILEY, Mr Henry Joseph" "BOXHALL, Mr Joseph Groves" ...
##  $ Age       : chr [1:66] "40" "36" "43" "28" ...
##  $ Class/Dept: chr [1:66] "Deck" "Deck" "Deck" "Deck" ...
##  $ Ticket    : chr [1:66] NA NA NA NA ...
##  $ Fare      : chr [1:66] NA NA NA NA ...
##  $ Group     : chr [1:66] NA NA NA NA ...
##  $ Ship      : chr [1:66] NA NA NA NA ...
##  $ Joined    : chr [1:66] "Southampton" "Southampton" "Southampton" "Belfast" ...
##  $ Job       : chr [1:66] "Able Seaman" "Able Seaman" "Master-at-arms" "4th. Officer" ...
##  $ Boat      : chr [1:66] "3" "16" "16" "2" ...
##  $ Body      : chr [1:66] NA NA NA NA ...
## List of 11
##  $ Name      : chr [1:325] "ABRAMS, Mr William" "ADAMS, Mr R." "ALLEN, Mr Henry" "ALLEN, Mr Ernest Frederick" ...
##  $ Age       : chr [1:325] "33" "26" "32" "24" ...
##  $ Class/Dept: chr [1:325] "Engine" "Engine" "Engine" "Engine" ...
##  $ Ticket    : chr [1:325] NA NA NA NA ...
##  $ Fare      : chr [1:325] NA NA NA NA ...
##  $ Group     : chr [1:325] NA NA NA NA ...
##  $ Ship      : chr [1:325] NA NA NA NA ...
##  $ Joined    : chr [1:325] "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr [1:325] "Fireman / Stoker" "Fireman / Stoker" "Fireman / Stoker" "Trimmer" ...
##  $ Boat      : chr [1:325] NA NA NA "B" ...
##  $ Body      : chr [1:325] NA NA "145" NA ...
## List of 11
##  $ Name      : chr [1:9] "ANDREWS, Mr Thomas" "CAMPBELL, Mr William Henry" "CHISHOLM, Mr Roderick Robert Crispin" "CUNNINGHAM, Mr Alfred Fleming" ...
##  $ Age       : chr [1:9] "39" "21" "40" "21" ...
##  $ Class/Dept: chr [1:9] "1st Class" "2nd Class" "1st Class" "2nd Class" ...
##  $ Ticket    : chr [1:9] "112050" "239853" "112051" "239853" ...
##  $ Fare      : chr [1:9] NA NA NA NA ...
##  $ Group     : chr [1:9] "H&W Guarantee Group" "H&W Guarantee Group" "H&W Guarantee Group" "H&W Guarantee Group" ...
##  $ Ship      : chr [1:9] NA NA NA NA ...
##  $ Joined    : chr [1:9] "Belfast" "Belfast" "Belfast" "Belfast" ...
##  $ Job       : chr [1:9] "Shipbuilder" NA "Draughtsman" "Fitter" ...
##  $ Boat      : chr [1:9] NA NA NA NA ...
##  $ Body      : chr [1:9] NA NA NA NA ...
## List of 13
##  $ Name      : chr [1:15] "GWYNN, Mr William Logan" "MARCH, Mr John Starr" "SMITH, Mr John Richard Jago" "WILLIAMSON, Mr James Bertram" ...
##  $ Age       : chr [1:15] "37" "50" "35" "35" ...
##  $ Class/Dept: chr [1:15] "Victualling" "Victualling" "Victualling" "Victualling" ...
##  $ Ticket    : chr [1:15] NA NA NA NA ...
##  $ Fare      : chr [1:15] NA NA NA NA ...
##  $ Group     : chr [1:15] "Postal Clerk" "Postal Clerk" "Postal Clerk" "Postal Clerk" ...
##  $ Ship      : chr [1:15] NA NA NA NA ...
##  $ Joined    : chr [1:15] "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr [1:15] "Postal Clerk" "Postal Clerk" "Postal Clerk" "Postal Clerk" ...
##  $ Boat      : chr [1:15] NA NA NA NA ...
##  $ Body      : chr [1:15] NA "225" NA NA ...
##  $           : chr [1:15] NA NA NA NA ...
##  $           : chr [1:15] NA NA NA NA ...
## List of 13
##  $ Name      : chr [1:69] "ALLARIA, Sig. Battista Antonio" "ASPESLAGH, Mr Georges" "BANFI, Sig. Ugo" "BASILICO, Sig. Giovanni" ...
##  $ Age       : chr [1:69] "22" "26" "24" "27" ...
##  $ Class/Dept: chr [1:69] "A la Carte" "A la Carte" "A la Carte" "A la Carte" ...
##  $ Ticket    : chr [1:69] NA NA NA NA ...
##  $ Fare      : chr [1:69] NA NA NA NA ...
##  $ Group     : chr [1:69] NA NA NA NA ...
##  $ Ship      : chr [1:69] NA NA NA NA ...
##  $ Joined    : chr [1:69] "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr [1:69] "Assistant Waiter" "Assistant Plateman" "Waiter" "Waiter" ...
##  $ Boat      : chr [1:69] NA NA NA NA ...
##  $ Body      : chr [1:69] "221" NA NA NA ...
##  $           : chr [1:69] NA NA NA NA ...
##  $           : chr [1:69] NA NA NA NA ...
## List of 11
##  $ Name      : chr [1:8] "BRAILEY, Mr W. Theodore Ronald" "BRICOUX, Mr Roger Marie" "CLARKE, Mr John Frederick Preston" "HARTLEY, Mr Wallace Henry" ...
##  $ Age       : chr [1:8] "24" "20" "30" "33" ...
##  $ Class/Dept: chr [1:8] "2nd Class" "2nd Class" "2nd Class" "2nd Class" ...
##  $ Ticket    : chr [1:8] "250654" "250654" "250654" "250654" ...
##  $ Fare      : chr [1:8] NA NA NA NA ...
##  $ Group     : chr [1:8] "Musician" "Musician" "Musician" "Musician" ...
##  $ Ship      : chr [1:8] NA NA NA NA ...
##  $ Joined    : chr [1:8] "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr [1:8] "Musician" "Musician" "Musician" "Musician" ...
##  $ Boat      : chr [1:8] NA NA NA NA ...
##  $ Body      : chr [1:8] NA NA "202" "224" ...
## List of 13
##  $ Name      : chr [1:431] "ABBOTT, Mr Ernest Owen" "AHIER, Mr Percy Snowden" "AKERMAN, Mr Albert" "AKERMAN, Mr Joseph Francis" ...
##  $ Age       : chr [1:431] "21" "20" "28" "35" ...
##  $ Class/Dept: chr [1:431] "Victualling" "Victualling" "Victualling" "Victualling" ...
##  $ Ticket    : chr [1:431] NA NA NA NA ...
##  $ Fare      : chr [1:431] NA NA NA NA ...
##  $ Group     : chr [1:431] NA NA NA NA ...
##  $ Ship      : chr [1:431] NA NA NA NA ...
##  $ Joined    : chr [1:431] "Southampton" "Southampton" "Southampton" "Southampton" ...
##  $ Job       : chr [1:431] "Lounge Pantry Steward" "Saloon Steward" "Steward" "Assistant Pantryman Steward" ...
##  $ Boat      : chr [1:431] NA NA NA NA ...
##  $ Body      : chr [1:431] NA NA NA "205" ...
##  $           : chr [1:431] NA NA NA NA ...
##  $           : chr [1:431] NA NA NA NA ...
## $`1er_clase.rds`
## NULL
## 
## $`2da_clase.rds`
## NULL
## 
## $`3era_clase.rds`
## NULL
## 
## $deck.rds
## NULL
## 
## $engine.rds
## NULL
## 
## $guarantee_group.rds
## NULL
## 
## $postal_clerk.rds
## NULL
## 
## $restaurant.rds
## NULL
## 
## $ships_orchesta.rds
## NULL
## 
## $victualling.rds
## NULL
#bind_rows es como rbind solo que optimizado por Hadley Wickham :) 
titanic <- bind_rows(ds)[, 1:num_cols]

Cambiemos nombres a minúmsculas y sin simbolillos raros /

names(titanic) <- str_replace_all(names(titanic), "/| ", "_") %>% 
  str_to_lower()
names(titanic)
##  [1] "name"       "age"        "class_dept" "ticket"     "fare"      
##  [6] "group"      "ship"       "joined"     "job"        "boat"      
## [11] "body"

Pasemos el dataframe a un objeto más eficiente

titanic <- tbl_df(titanic)
titanic
  1. Genera las siguientes variables: survived, name, last_name, sex ¿Se te ocurre alguna forma de definir survived?
  2. Arregla la columna de precio, edad \(\rightarrow\) como verás en aquellos ayeres el dinero no tenía decimales… aquí nos puede ayudar Michael :P British money. Por otro lado, en la columna edad hay niños con menos de 1 año expresados con el número de meses seguido de m

Haremos el 3 y el 4 juntos :)

titanic <- titanic %>% 
  separate(name, into=c("last_name", "name"), sep=",", extra="drop") %>%
  separate(fare, into=c("pounds", "shillings", "pence"), sep=" ", extra="drop") %>%
  separate(age, into=c("age", "units"), sep=2, extra="drop") %>%
  mutate(sex=ifelse(grepl("Miss|Mrs|Mme.|Lady|Doña|Ms", name), 'F',
                      ifelse(grepl("Mr|Sir|Sig|Dr|Master|Captain|Major|Rev.|Colonel|Fr|Don.", name), 'M', NA))) %>% 
  mutate(boat_location=ifelse(as.integer(boat) %in% c(9:16), 'Popa', 
                              ifelse(boat %in% c(LETTERS[1:4]) | as.integer(boat) %in% c(1:8), 'Proa', NA))) %>% 
  mutate(age=ifelse(units == "m", 1, as.integer(age))) %>% 
  mutate(survived=!is.na(boat)) %>%
  dplyr::select(-c(shillings, pence, body, units)) %>%
  mutate(pounds=str_replace(pounds, "£", "") %>% as.integer()) %>%
  mutate(class_dept=as.factor(class_dept), group=as.factor(group), ship=as.factor(ship),
         joined=as.factor(joined), job=as.factor(job), boat=as.factor(boat),
         sex=as.factor(sex), boat_location=as.factor(boat_location))
## Warning: Too few values at 709 locations: 2, 3, 4, 5, 6, 13, 14, 15, 23,
## 27, 30, 31, 32, 34, 35, 37, 38, 40, 41, 45, ...
## Warning in as.integer(boat) %in% c(9:16): NAs introduced by coercion
## Warning in as.integer(boat) %in% c(1:8): NAs introduced by coercion
## Warning in ifelse(units == "m", 1, as.integer(age)): NAs introduced by
## coercion

Que bonito es dplyr y tidyr (ノ^_^)ノ

summary(titanic)
##   last_name             name                age              class_dept 
##  Length:2240        Length:2240        Min.   : 1.00   3rd Class  :708  
##  Class :character   Class :character   1st Qu.:22.00   Victualling:436  
##  Mode  :character   Mode  :character   Median :29.00   1st Class  :327  
##                                        Mean   :30.02   Engine     :325  
##                                        3rd Qu.:36.75   2nd Class  :299  
##                                        Max.   :74.00   (Other)    :135  
##                                        NA's   :26      NA's       : 10  
##     ticket              pounds                       group     
##  Length:2240        Min.   :  3.00   H&W Guarantee Group:  18  
##  Class :character   1st Qu.:  7.00   Musician           :  16  
##  Mode  :character   Median : 14.00   Postal Clerk       :  10  
##                     Mean   : 33.32   Servant            :  43  
##                     3rd Qu.: 31.00   NA's               :2153  
##                     Max.   :512.00                             
##                     NA's   :949                                
##    ship              joined                   job            boat     
##  NA's:2240   Belfast    : 207   Fireman / Stoker: 161   13     :  52  
##              Cherbourg  : 274   General Labourer: 161   15     :  43  
##              Queenstown : 120   Saloon Steward  : 127   C      :  42  
##              Southampton:1626   Trimmer         :  73   14     :  38  
##              NA's       :  13   Farm Labourer   :  49   4      :  38  
##                                 (Other)         :1011   (Other): 369  
##                                 NA's            : 658   NA's   :1658  
##    sex       boat_location  survived      
##  F   : 484   Popa: 280     Mode :logical  
##  M   :1742   Proa: 302     FALSE:1658     
##  NA's:  14   NA's:1658     TRUE :582      
##                                           
##                                           
##                                           
## 
  1. Agrega una columna de age que sea categórica. Definamos 3 categorías:
  • infante: si es menor de 18 años
  • adulto: entre 18 y 65 años
  • adulto mayor: si es mayor a 65 años
titanic <- titanic %>% mutate(age = ifelse(age <= 18, "infante",
                                           ifelse(age > 65, "adulto mayor", 
                                                  "adulto")))
#verifiquemos
titanic
  1. Ajusta a precios del día de hoy (Por ejemplo usa esta página) ¿En que clase hubieras viajado? ¿Cuál era tu probilidad de supervivencia?
ggplot(titanic, aes(pounds)) + 
  geom_histogram(binwidth = 30, na.rm=T) +
  theme_bw()

titanic <- titanic %>% 
    group_by(ticket) %>% 
    mutate(pounds_per_ticket = round(pounds/n())) %>% 
  ungroup()

titanic
titanic %>% filter(class_dept %in% c('1st Class', '2nd Class', '3rd Class')) %>%
  ggplot(aes(pounds_per_ticket)) + 
  geom_histogram(binwidth = 10) + 
  facet_grid(class_dept~., scales = "free_y") +
  theme_bw()
## Warning: Removed 43 rows containing non-finite values (stat_bin).

\(\rightarrow\) Aproximadamente 10 libras de 1912 son 1,080 libras actuales, 50 libras son 5,400 y 100 libras son 10,800 libras al 2017

  1. Observando la distribución de botes que se muestra en la figura ¿Qué puedes decir sobre como se utilizaron? ¿Coincide con la película de Titanic de James Cameron?
titanic %>% 
    group_by(boat_location) %>% 
    summarise(n=n())

Eso no dice mucho…

titanic %>%
    group_by(boat) %>%
    summarise(n=n()) %>%
    arrange(desc(n))

Los botes del 1 al 16 tenían una capacidad de 65 personas, los botes del A al D tenían una capacidad de 45 personas. (Fuente).

Fuente de los datos:

Encyclopedia Titanica

Berka

instalar <- function(paquete) {

    if (!require(paquete,character.only = TRUE, quietly = TRUE, warn.conflicts = FALSE)) {
        install.packages(as.character(paquete), dependecies = TRUE, repos = "http://cran.us.r-project.org")
        library(paquete, character.only = TRUE, quietly = TRUE, warn.conflicts = FALSE)
    }
}

paquetes <- c('lubridate', 'magrittr', 'ggvis', 'dplyr', 'tidyr', 'readr', 'rvest', 
              'ggplot2', 'stringr', 'ggthemes', 'googleVis', 'shiny', 'tibble', 'vcd', 'vcdExtra',
              'GGally', 'readODS', 'readxl', "RSQLite")

lapply(paquetes, instalar);

Los datos están en http://sorry.vse.cz/~berka/challenge/pkdd1999/data_berka.zip, descárgalos y guárdalos en data/berka, fueron usados en la competencia de PKDD de 1999.

Meta

“El banco busca mejorar sus servicios, Por ejemplo, los admnistradores del banco tienen únicamente una vaga idea de quién es un buen cliente (al cual ofrecerle más servicios) y quién es un mal cliente (alguien al que hay que cuidar para minimizar las pérdidas del banco). Afortunadamente, el banco almacena datos de sus clientes, sus cuentas (transacciones), préstamos que ya se otorgaron, tarjetas de crédito dadas. Los administradaores del banco esperan mejorar su entendimiento de los clientes y mejorar así sus servicios. Una mera aplicación de una herramienta de discovery no los va a convencer.”

Esquema

Account

Características de la cuenta Archivo: account

Column Description Notes
account_id Identification of the account
district_id Location of the branch
date Date of the account’s creation In the form: YYMMDD
frequency Frequency of statement issuance “POPLATEK MESICNE” - Monthly Issuance
“POPLATEK TYDNE” - Weekly Issuance
“POPLATEK PO OBRATU” - Issuance After Transaction
Client

Características del cliente Archivo: client

Column Description Notes
client_id Client Identifier
birth number Birthday and Sex The value is in the form: YYMMDD (for men)
The value is in the form: YYMM+50DD (for women)
Where YYMMDD is the date of birth
district_id Address of the client
Disposition

Relaciona una cuenta con un cliente (los derechos de los clientes para operar cuentas) Archivo: disp

Column Description Notes
disp_id Record Identifier
client_id Client Identifier
account_id Account Identifier
type Type of Disposition (owner/user) Only owner can issue permanent orders and ask for a loan
Loan

Préstamos dabos a una cuenta Archivo: loan

Column Description Notes
disp_id Record Identifier
loan_id Record Identifier
account_id Account Identifier
date Date when loan was granted In the form: YYMMDD
amount Amount of Loan
duration Duration of Loan
payments Monthly Payments on Loan
status Status in paying off the loan ‘A’ stands for contract finished, no problems
‘B’ stands for contract finished, loan not payed
‘C’ stands for running contract, OK thus-far
‘D’ stands for running contract, client in debt
Order

Características de una orden de pago Archivo: order

Column Description Notes
order_id Record Identifier
account_id Account the order is issued for
bank_to Bank of the recipient Each bank has a unique two-letter code
account_to Account of the recipient
amount Amount debited from order account
K_symbol Characterization of the payment ‘POJISTNE’ stands for Insurance Payment
‘SIPO’ stands for Household Payment
‘LEASING’ stands for Leasing Payment
‘UVER’ stands for Loan Payment
Transaction

Transacciones en las cuentas Archivo: trans

Column Description Notes
trans_id Record Identifier
account_id Account the transaction is issued on
date Date of transaction In the form: YYMMDD
type debit/credit transaction ‘PRIJEM’ stands for Credit
‘VYDAJ’ stands for Debit (withdrawal)
operation Mode of Transaction ‘VYBER KARTOU’ stands for Credit Card Withdrawal
‘VKLAD’ stands for Credit in Cash
‘PREVOD Z UCTU’ stands for Collection from Another Bank
‘VYBER’ stands for Withdrawal in Cash
‘PREVOD NA UCET’ stands for Remittance to Another Bank
amount Amount of Transaction
balance Balance of Account after Transaction
K_Symbol Characterization of Transaction ‘POJISTNE’ stands for Insurance Payment
‘SLUZBY’ stands for Payment of Statement
‘UROK’ stands for Interest Credited
‘SANKC. UROK’ stands for Sanction Interest if Negative Balance
‘SIPO’ stands for Household Payment
‘DUCHOD’ stands for Old-age Pension Payment
‘UVER’ stands for Loan Payment
bank Bank of the partner Each bank has unique two-letter code
account Account of the partner
Demographic

Características de un distrito Archivo: district

Column Description Notes
A1 district_id District Identifier
A2 District Name
A3 Region
A4 No. of Inhabitants
A5 No. of Municipalities with inhabitants < 499
A6 No. of Municipalities with inhabitants 500-1999
A7 No. of Municipalities with inhabitants 2000-9999
A8 No. of Municipalities with inhabitants > 10000
A9 No. of Cities
A10 Ratio of urban inhabitants
A11 Average Salary
A12 Unemployment rate in 1995
A13 Unemployment rate in 1996
A14 No. of Enterpreneurs per 1000 inhabitants
A15 No. of Crimes commited in 1995
A16 No. of Crimes commited in 1996
Credit card

Tarjeta de crédito otorgada a una cuenta Archivo: card

Column Description Notes
card_id Card Identifier
disp_id Disposition to an account
type Type of card Types are ‘Junior’, ‘Classic’, and ‘Gold’
issued Date card was issued In the format: YYMMDD

Características importantes

  • Cada cuenta tiene características estáticas (account) y dinámicas (pagos, créditos, balances) dados en order y transaction
  • client características de personas que pueden manipular cuentas.
  • Los clientes pueden tener varias cuentas, y viceversa. La relación entre clientes y cuentas está en disposition
  • Los servicios del banco están descritos en loan y credit_card
  • Una cuenta puede tener varias tdc
  • Máximo un préstamo se le puede otorgar a una cuenta.
  • La tabla de demografía contiene informacióń sobre los distritos, se puede deducir información adicional sobre los clientes a partir de esta tabla.

Esquema Entidad-Relación

Berka dataset

Berka dataset

Base de datos

La utilización de una base de datos (Relacional, Grafos, Columnar, etc), siempre es recomendable sobre el uso de archivos (la excepción es un Apache Hadoop, pero eso lo veremos en Métodos de Gran Escala ╭(◔ ◡ ◔)/).

Las ventajas de la utilización de una base de datos son las siguientes:

  • Es posible manejar volúmenes de datos más grandes que memoria.
  • Velocidad de acceso a los datos
  • Facilidad de manipulación con lenguajes relacionales (como SQL)
  • Colaboración
    • Varias personas pueden accesar a los datos
    • Estas personas no tienen por qué usar el mismo lenguaje (!)

Usaremos sqlite3 para este ejemplo. SQLite es una base de datos relacional, local (por lo que algunas de las ventajas recién mencionadas no aplican ¿Cuál?)

  • Instalar SQLite3 en Ubuntu 16.04:
sudo apt-get update
sudo apt-get install sqlite3
  • Instalar SQLite3 en MAC: si tienes OS Leopard en adelante, no necistas instalarlo, ya viene. Si tienes un OS más viejito Mac y Windows -nunca lo he hecho!-

  • SQLite3 CLI

NOTA: Lo que sigue ocurre en la línea de comandos

Para abrir una base de datos sqlite en memoria

$ sqlite3
SQLite version 3.13.0 2016-05-18 10:57:30
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> 
## Emebelliciendo el output
sqlite> .header on
sqlite> .mode column


## Importar archivos (accounts.asc) a la base de datos a la tabla (accounts)
## ¡Usa plural para las tablas!
sqlite> .separator ";"
sqlite> .import account.asc accounts

## Verifica que la tabla exista
sqlite> .tables
acccounts

## Estructura de la tabla
sqrlite> PRAGMA table_info(accounts);
cid         name        type        notnull     dflt_value  pk        
----------  ----------  ----------  ----------  ----------  ----------
0           account_id  TEXT        0                       0         
1           district_id TEXT        0                       0         
2           frequency   TEXT        0                       0         
3           date        TEXT        0                       0    

## veamos qué hay adentro
sqrlite> select * from accounts limit 5;
account_id  district_id  frequency         date      
----------  -----------  ----------------  ----------
576         55           POPLATEK MESICNE  930101    
3818        74           POPLATEK MESICNE  930101    
704         55           POPLATEK MESICNE  930101    
2378        16           POPLATEK MESICNE  930101    
2632        24           POPLATEK MESICNE  930102  

## Guardar la tabla
sqlite> .save berka.raw

La siguiente vez que quieras consultar la base de datos

$ sqlite3 berka.raw
SQLite version 3.11.0 2016-02-15 17:29:24
Enter ".help" for usage hints.
sqlite> 

O si se te olvida poner el nombre del archivo:

$ sqlite3
SQLite version 3.11.0 2016-02-15 17:29:24
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open berka.raw

Ejercicio: To Raw

Cargaremos los datos en berka.raw

for db in *asc;
do
table=${db%.*}s
if [ "$db" = "trans.asc" ]; then
table="transactions"
fi
echo -e ".separator ';'\n.import ${db} ${table}" | sqlite3 berka.raw 
done

Puedes conectarte a tu base de sqlite con dplyr , necesitarás el paquete RSQLite

library(RSQLite)

berka_db <- src_sqlite(path="~/Documents/itam/introduction_to_ds/intro_to_ds/data/berka/berka.raw", 
           create=FALSE)
db_list_tables(berka_db$con)
## [1] "accounts"     "cards"        "clients"      "disps"       
## [5] "districts"    "loans"        "orders"       "transactions"
accounts_tbl <- tbl(berka_db, "accounts")
clients_tbl <- tbl(berka_db, "clients")
dispositions_tbl <- tbl(berka_db, "disps")

accounts_tbl %>% group_by(district_id) %>%
  summarise(count=n()) %>% 
  arrange(desc(count)) %>%
  collect()

Ejercicio: Raw to Clean

  1. Verifica que cada account tenga un owner

En la tabla disposition viene el atributo type que puede tener 2 valores distintos: OWNER y DISPONENT

sqlite3> select distinct(type) 
from disps;

O en dplyr:

dispositions_tbl %>% distinct(type) %>% collect()
sqlite3> select *, count(*)
from disps
where type = 'ONWER'
group by account_id
having count(*) > 1
order by count(*) desc
limit 5;
dispositions_tbl %>% filter(type == 'OWNER') %>% 
  group_by(account_id) %>% 
  summarise(n=n()) %>%
  arrange(desc(n)) %>% collect()
  • Los registros de orders y loans están duplicados en transactions. Es decir los regsitros de ordery loan están dentro de transactions (por ejemplo, Los registros de loan en tran están identificados por el k_symbol LP)

  • Traduce los campos del checo al inglés.
  • En client cambia BirthNumber a sex y age
  • Discretiza age en Youth (0-24), Adult (25-45), Middle-age (46-64) y Senior (> 65)
  • En disposition cambia de Dispondent -> User
  • En loan discretiza usando alguna heuristica amount, duration y payments
  • En transaction traduce la columna type, operation, k_symbol
  • Ajustamos los nombres de las tablas a plural, los *_id a singular.
  • Guardemos los datos en berka.clean

Limpieza retos técnicos

Imágenes tomadas de TAMR






Primer acercamiento a Linage and Procedence: Columnas de procedencia -te recomiendo leer este blog-

Un aspecto que siempre es olvidado, o que no se considera importante debido a los blogs, es el versionado de control de los datos

Esto se puede implementar, agregando columnas para indicar de donde vienen los datos, o con qué procedimiento de limpieza se generaron, etc.

Se puede utilizar el mismo id del código del ETL guardado en github. ¿Cómo implementarías esto? ¿Puedes trazar de dónde provienen tus datos?